Подтвердить что ты не робот

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 является незаконным?

4b9b3361

Ответ 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();
    }

}

но хотелось бы узнать о лучших способах.