mvc:annotation-driven标签

1、mvc:annotation-driven简介

  • SpringMVC为我们提供的一个标签 <mvc:annotation-driven/>,这个标签会帮我们注入很多关键而实用的bean(主要注册HandlerMappings-路径匹配器、HandlerAdapters-路径匹配适配器、HandlerExceptionResolvers-异常解析器、AntPathMatcher-路径解析器、UrlPathHelper-请求路径获取帮助类等beans,提前为mvc做好基础的准备),并提供以下支持:

    • 支持使用 ConversionService 实例对表单参数进行类型转换。
    • 支持使用 @NumberFormat annotation、@DateTimeFormat注解完成数据类型的格式化。
    • 支持使用 @Valid注解对 JavaBean 实例进行 JSR 303 验证。
    • 支持使用 @RequestBody和@ResponseBody注解。
  • 解析这些标签则是由各种解析器完成的,它们都实现了一个名为BeanDefinitionParser的接口:

    1
    2
    3
    4
    public interface BeanDefinitionParser {
    @Nullable
    BeanDefinition parse(Element element, ParserContext parserContext);
    }

    其中的一个实现类AnnotationDrivenBeanDefinitionParser则是用来解析<mvc:annotation-driven />标签的:

    部分代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext context) {
    Object source = context.extractSource(element);
    XmlReaderContext readerContext = context.getReaderContext();

    CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
    context.pushContainingComponent(compDefinition);

    RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);
    //生成RequestMappingHandlerMapping组件对象
    RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
    handlerMappingDef.setSource(source);
    handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    //优先级设置为最高
    handlerMappingDef.getPropertyValues().add("order", 0);
    handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

    if (element.hasAttribute("enable-matrix-variables")) {
    boolean enableMatrixVariables = Boolean.parseBoolean(element.getAttribute("enable-matrix-variables"));
    handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
    }

    configurePathMatchingProperties(handlerMappingDef, element, context);
    readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);

    RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
    handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);

    RuntimeBeanReference conversionService = getConversionService(element, source, context);
    RuntimeBeanReference validator = getValidator(element, source, context);
    RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);

    RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
    bindingDef.setSource(source);
    bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    bindingDef.getPropertyValues().add("conversionService", conversionService);
    bindingDef.getPropertyValues().add("validator", validator);
    bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

    ManagedList<?> messageConverters = getMessageConverters(element, source, context);
    ManagedList<?> argumentResolvers = getArgumentResolvers(element, context);
    ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, context);
    String asyncTimeout = getAsyncTimeout(element);
    RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
    ManagedList<?> callableInterceptors = getInterceptors(element, source, context, "callable-interceptors");
    ManagedList<?> deferredResultInterceptors = getInterceptors(element, source, context, "deferred-result-interceptors");

    RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
    handlerAdapterDef.setSource(source);
    handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
    handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
    handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
    addRequestBodyAdvice(handlerAdapterDef);
    addResponseBodyAdvice(handlerAdapterDef);

    if (element.hasAttribute("ignore-default-model-on-redirect")) {
    Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
    handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
    }
    if (argumentResolvers != null) {
    handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
    }
    if (returnValueHandlers != null) {
    handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
    }
    if (asyncTimeout != null) {
    handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
    }
    if (asyncExecutor != null) {
    handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
    }

    handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
    handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
    readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);

    RootBeanDefinition uriContributorDef =
    new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
    uriContributorDef.setSource(source);
    uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
    uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
    String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
    readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);

    RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
    csInterceptorDef.setSource(source);
    csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
    RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
    mappedInterceptorDef.setSource(source);
    mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
    mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
    String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);

    RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
    methodExceptionResolver.setSource(source);
    methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
    methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
    methodExceptionResolver.getPropertyValues().add("order", 0);
    addResponseBodyAdvice(methodExceptionResolver);
    if (argumentResolvers != null) {
    methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
    }
    if (returnValueHandlers != null) {
    methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
    }
    String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);

    RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
    statusExceptionResolver.setSource(source);
    statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    statusExceptionResolver.getPropertyValues().add("order", 1);
    String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);

    RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
    defaultExceptionResolver.setSource(source);
    defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    defaultExceptionResolver.getPropertyValues().add("order", 2);
    String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);

    context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
    context.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
    context.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
    context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
    context.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));
    context.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));
    context.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));

    // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
    MvcNamespaceUtils.registerDefaultComponents(context, source);

    context.popAndRegisterContainingComponent();

    return null;
    }

2、DispatcherServlet路径映射问题

  • DispatcherServlet是Spring提供给开发者的servlet,在整个过程中DispatcherServlet承当了一个中心控制器的角色来处理各种请求。在请求到达之前还需要经过web.xml,我们在自己应用的web.xml中会配置一个前端控制器DispatcherServlet:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!--启动SpringMVC容器-->
    <!--配置前端控制器能拦截所有请求,并智能派发-->
    <!--这个前端控制器是一个servlet,应该在web.xml中配置这个servlet来拦截所有请求-->
    <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!--servlet启动加载,servlet原本是第一次访问就创建对象-->
    <!--load-on-startup表示服务器启动的时候创建对象,值越小优先级越高,越先创建对象-->
    <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>

    而我们在应用中配置的web.xml都继承于tomcat里的web.xml,所以当我们配置不恰当的路径映射时可能会出现问题。tomcat中的xml配置了两个Servlet,分别是DefaultServlet(用来处理静态资源文件请求)和JspServlet(用来处理jsp请求),且jsp请求必须由JspServlet来处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
    <param-name>debug</param-name>
    <param-value>0</param-value>
    </init-param>
    <init-param>
    <param-name>listings</param-name>
    <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
    <param-name>fork</param-name>
    <param-value>false</param-value>
    </init-param>
    <init-param>
    <param-name>xpoweredBy</param-name>
    <param-value>false</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
    </servlet>

    如果上方自己的应用中配置DispatcherServlet的为”/“,它会对tomcat中的DefaultServlet配置进行覆盖,导致不能访问webapp下的静态资源;而如果在自己的应用中配置DispatcherServlet的为”/*”时(/*的范围更大,是拦截所有请求),会同时覆盖DefaultServlet和JspServlet的配置,导致连jsp资源都无法访问了,因为jsp请求不再交由tomcat处理,而是交给DispatchServlet处理,而DispatchServlet无法处理jsp页面请求。

3、使用标签解决静态资源请求

  • 为了能够访问静态资源,我们会添加如下标签:

    1
    <mvc:default-servlet-handler/>

    但添加完后,会发现虽然静态资源能够访问了,但是发送@RequestMapping的请求却失败了,而如果再添加个标签:

    1
    <mvc:annotation-driven/>

    会发现静态资源和动态资源都能访问了。

4、标签原理

  • 有无此标签会影响到对服务器静态和动态资源的访问问题,现在用https://perfectcode.top/2020/12/20/SpringMVC%E8%BF%90%E8%A1%8C%E6%B5%81%E7%A8%8B/的测试用例,在springmvc.xml中进行不同的标签配置,在DispatcherServlet类的doDispatch方法上打上断点,开启debug模式后发起/hello请求后来到此方法,查看此类中handlerMappings和handlerAdapters的信息:

    • ①没有配置<mvc:default-servlet-handler/><mvc:annotation-driven/>标签

      • 可见在此情况下:

        • HandlerMapping:BeanNameUrlHandlerMapping,RequestMappingHandlerMapping,RouterFunctionMapping
        • HandlerAdapter:HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter,RequestMappingHandlerAdapter,HandlerFunctionAdapter
      • 使用@RequestMapping映射的请求(通过RequestMappingHandlerMapping找到处理器HandlerMethod):

        再通过处理器HandlerMethod找到适配器RequestMappingHandlerAdapter,此适配器帮我们执行目标方法:

      • 请求静态资源(由于所有的HandlerMapping中没有找到静态资源对应的映射信息,所以找不到任何处理器,直接返回错误页面):

        @RequestMapping映射的请求可以由RequestMappingHandlerMapping、RequestMappingHandlerAdapter两个组件处理;而静态资源请求没有组件可以处理。所以此情况下@RequestMapping映射的请求可以处理而静态资源请求无法处理。

    • ②只配置<mvc:default-servlet-handler/>标签

      • 可见在此情况下:

        • HandlerMapping:BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping
        • HandlerAdapter:HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter
      • 使用@RequestMapping映射的请求(通过SimpleUrlHandlerMapping找到处理器DefaultServletHttpRequestHandler,因为此时所有请求都会交给DefaultServletHttpRequestHandler,即会交给tomcat处理)

        再通过处理器DefaultServletHttpRequestHandler找到适配器HttpRequestHandlerAdapter,但由于tomcat中没有任何servlet可以处理此请求信息,最终转发到错误页面。

      • 请求静态资源(通过SimpleUrlHandlerMapping找到处理器DefaultServletHttpRequestHandler):

        再通过处理器DefaultServletHttpRequestHandler找到适配器HttpRequestHandlerAdapter。

        SimpleUrlHandlerMapping使用的效果就是调用tomcat的DefaultServlet处理请求, 相当于DispatcherServlet把请求转给了tomcat的DefaultServlet处理,DefaultServlet能够处理静态资源请求,但它肯定不知道 DispatcherServlet中映射的请求。所以此情况下@RequestMapping映射的请求无法处理而静态资源请求可以处理。

    • ③只配置<mvc:annotation-driven/>标签

      • 可见在此情况下:

        • HandlerMapping:RequestMappingHandlerMapping,BeanNameUrlHandlerMapping
        • HandlerAdapter:RequestMappingHandlerAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter

        这和没有配置任何标签的情况是一致的,**@RequestMapping映射的请求可以处理而静态资源请求无法处理。**

    • ④同时配置<mvc:default-servlet-handler/><mvc:annotation-driven/>标签

      • 可见在此情况下:

        • HandlerMapping:RequestMappingHandlerMapping,BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping
        • HandlerAdapter:HttpRequestHandlerAdapter,SimpleControllerHandlerAdapterRequestMappingHandlerAdapter
      • 使用@RequestMapping映射的请求(通过RequestMappingHandlerMapping找到处理器HandlerMethod):

        再通过处理器HandlerMethod找到适配器RequestMappingHandlerAdapter:

      • 请求静态资源(通过SimpleUrlHandlerMapping找到处理器DefaultServletHttpRequestHandler):

        再通过处理器DefaultServletHttpRequestHandler找到适配器HttpRequestHandlerAdapter。

        @RequestMapping映射的请求可以由RequestMappingHandlerMapping、RequestMappingHandlerAdapter两个组件处理;静态资源请求可以由SimpleUrlHandlerMapping、HttpRequestHandlerAdapter两个组件处理。所以此情况下@RequestMapping映射的请求和静态资源请求都能处理。