【问题标题】:Spring MVC @RequestMapping InheritanceSpring MVC @RequestMapping 继承
【发布时间】:2011-07-13 05:21:27
【问题描述】:

来自 Struts2 我习惯于在超类(或 package-info.java)上声明 @Namespace 注释,继承类随后会获取其祖先的 @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 路径 [/tweeter/updateStatus] 映射到处理程序 '推特控制器'

是否有我可以启用的设置来扫描超类中的@RequestMapping 注释并构造正确的路径?

我还认为在package-info.java 中的包上定义@RequestMapping 是非法的?

【问题讨论】:

    标签: java spring-mvc


    【解决方案1】:

    下面基本上变成/tweeter/updateStatus而不是/user/tweeter/updateStatus

    public abstract class TwitterController extends AbstractTwitterController {
    
        @RequestMapping(value = "/updateStatus")    
        public String updateStatus() {
            ....
        }
    }
    

    这是预期的行为,因为您已经覆盖了在 AbstractControllerAbstractUserController 中声明的原始 @RequestMapping

    事实上,当您声明 AbstractUserController 时,它还为 AbstractController 覆盖了 @RequestMapping。它只是让您产生一种错觉,即 AbstractController 中的 / 已被继承。

    “我可以启用一个设置来扫描超类中的@RequestMapping 注释并构造正确的路径吗?”从来没听说过。

    【讨论】:

    • 您是否有任何其他关于如何实现这一目标的建议。在我看来,不得不将 /x/y/z 放在子类环境中是一种浪费。然后,如果我决定将 /x/ 更改为 /k/,我必须转到代码中定义它的所有位置并手动更改它!
    • @chris:这对我不起作用。是否涉及配置步骤,还是只是注释问题?基本控制器确实与扩展基本控制器的控制器在同一个包中。但是,基本控制器的 URI/RequestMapping 被忽略和/或找不到。 “org.springframework.web.servlet.DispatcherServlet:947 - 找不到带有 URI 的 HTTP 请求的映射”。所以,如果我只使用扩展控制器的“基础”RequestMapping,就可以了。如果我尝试使用基控制器继承的 RequestMapping,它找不到 URI。
    • 值得明确说明超类路径仍然是可路由的。也就是说,覆盖一个类并提供新的@RequestMappings 不会覆盖或替换超类的@RequestMappings,它只是添加了额外的。例如,在上面的 OP 问题中,这意味着所有这些路径都是有效的:/user/dashboard/twitter/updateStatus
    • 那将是推特,而不是推特。 :)
    【解决方案2】:

    根据Modifying @RequestMappings on startup中解释的技术, 是的,可以按照您想要的方式从超类构造 URL 模式。

    本质上,您必须继承 RequestMappingHandlerMapping(很可能是您的 HandlerMapping 实现,但请先检查) 并覆盖受保护的getMappingForMethod 方法。 一旦渲染变得可行,您就可以完全控制 URL 模式的生成。

    从您给出的示例中,并不完全清楚确切的合并策略,例如,如果您希望拥有什么路径 一个超类AbstractTwitterController 也实现了updateStatus() 方法和它自己的@RequestMapping,或者你想如何在层次结构中连接URL 模式,自上而下或自下而上,(我假设是前者), 但是,希望下面的 sn-p 能给你一些想法:

        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的实例化。在 Internet 上,有很多关于各种配置策略的示例。 但是,使用 JavaConfig,请记住,如果您在 @Configuration 集中提供 WebMvcConfigurationSupport,那么您的 @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();
        }
    
    }
    

    但想了解更好的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-17
      • 1970-01-01
      相关资源
      最近更新 更多