Spring MVC @RequestMapping Inheritance
Начиная с Struts2, я привык объявлять аннотацию @Namespace
для суперклассов (или package-info.java
), а наследующие классы впоследствии будут зацикливаться на значении в аннотации @Namespace
своих предков и довести его до запроса путь для действия. Теперь я пытаюсь сделать что-то подобное в Spring MVC с помощью аннотации @RequestMapping
следующим образом (код сокращен для краткости):
package au.test
@RequestMapping(value = "/")
public abstract class AbstractController {
...
}
au.test.user
@RequestMapping(value = "/user")
public abstract class AbstractUserController extends AbstractController {
@RequestMapping(value = "/dashboard")
public String dashboard() {
....
}
}
au.test.user.twitter
@RequestMapping(value = "/twitter")
public abstract class AbstractTwitterController extends AbstractUserController {
...
}
public abstract class TwitterController extends AbstractTwitterController {
@RequestMapping(value = "/updateStatus")
public String updateStatus() {
....
}
}
-
/
работает как ожидается
-
/user/dashboard
работает как ожидалось
- Однако, если бы я ожидал, что
/user/twitter/updateStatus
будет работать, это не будет и проверка журналов, я могу увидеть запись в журнале, которая выглядит примерно так:
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping - URL-адрес сопоставленного URL [/tweeter/updateStatus] на обработчике 'TwitterController'
Можно ли включить параметр, который будет сканировать суперклассы для аннотаций @RequestMapping
и построить правильный путь?
Также я полагаю, что определение @RequestMapping
в пакете в package-info.java
является незаконным?
Ответы
Ответ 1
Следующее в основном становится /tweeter/updateStatus
, а не /user/tweeter/updateStatus
public abstract class TwitterController extends AbstractTwitterController {
@RequestMapping(value = "/updateStatus")
public String updateStatus() {
....
}
}
Это ожидаемое поведение, поскольку вы переопределили оригинальный @RequestMapping
, который вы указали в AbstractController
и AbstractUserController
.
Фактически, когда вы объявили, что AbstractUserController
, он также переопределяет @RequestMapping
для AbstractController
. Это просто дает вам иллюзию, что/из AbstractController
унаследовано.
"Есть ли параметр, который я могу включить, который будет сканировать суперклассы для аннотаций @RequestMapping
и построить правильный путь?" Не то, что я знаю из.
Ответ 2
Согласно методике, описанной в Изменение @RequestMappings при запуске,
да, возможно создать шаблон URL из суперклассов так, как вы хотите.
В сущности, вы должны подклассифицировать RequestMappingHandlerMapping (скорее всего, это будет ваш HandlerMapping, но сначала проверьте)
и переопределить защищенный метод getMappingForMethod
.
Как только это станет возможным, вы полностью контролируете создание шаблонов URL.
Из примера, который вы дали ему, не полностью очистить точную политику слияния, например, какой путь вы хотите иметь, если
суперкласс AbstractTwitterController
также реализует метод updateStatus()
со своим собственным @RequestMapping
или каким образом вы хотите объединить шаблоны URL-адресов по иерархии, сверху вниз или снизу вверх (я предположил, что первый ниже),
но, надеюсь, следующий фрагмент даст вам несколько идей:
private static class PathTweakingRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo methodMapping = super.getMappingForMethod(method, handlerType);
if (methodMapping == null)
return null;
List<String> superclassUrlPatterns = new ArrayList<String>();
boolean springPath = false;
for (Class<?> clazz = handlerType; clazz != Object.class; clazz = clazz.getSuperclass())
if (clazz.isAnnotationPresent(RequestMapping.class))
if (springPath)
superclassUrlPatterns.add(clazz.getAnnotation(RequestMapping.class).value()[0]);// TODO handle other elements in the array if necessary
else
springPath = true;
if (!superclassUrlPatterns.isEmpty()) {
RequestMappingInfo superclassRequestMappingInfo = new RequestMappingInfo("",
new PatternsRequestCondition(String.join("", superclassUrlPatterns)), null, null, null, null, null, null);// TODO implement specific method, consumes, produces, etc depending on your merging policies
return superclassRequestMappingInfo.combine(methodMapping);
} else
return methodMapping;
}
}
Еще один хороший вопрос - как перехватить экземпляр RequestMappingHandlerMapping
. В Интернете существует множество различных примеров для различных стратегий конфигурации.
Однако с JavaConfig помните, что если вы предоставите WebMvcConfigurationSupport
в своем наборе @Configuration
, то ваш @EnableWebMvc
(явный или неявный) перестанет работать. Я закончил со следующим:
@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration{
@Configuration
public static class UnconditionalWebMvcAutoConfiguration extends WebMvcAutoConfiguration {//forces @EnableWebMvc
}
@Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new PathTweakingRequestMappingHandlerMapping();
}
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return super.requestMappingHandlerMapping();
}
}
но хотелось бы узнать о лучших способах.