spring mvc 配置<mvc:annotation-driven/>失效了?

倖福魔咒の 提交于 2019-12-02 16:17:01

版本:spring 3.0以上

项目中有两个spring的配置xml,如下

${project.dir}\src\main\resources\META-INF\spring\applicationContext-service-database.xml
${project.dir}\src\main\webapp\WEB-INF\database-servlet.xml

项目中的web.xml

${project.dir}\src\main\webapp\WEB-INF\web.xml

web.xml的配置如下:


<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:META-INF/spring/applicationContext-service-database.xml
    </param-value>
</context-param> 
<listener>
    <listener-class> org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
 
 
<servlet>
    <servlet-name>database</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>database</servlet-name>
    <url-pattern>/api/*</url-pattern>
</servlet-mapping>

1、ContextLoaderListener加载applicationContext-service-database.xml

2、DispatcherServlet加载database-servlet.xml。如果不配置contextConfigLocation的话,DispatcherServlet会默认查找${project.dir}\src\main\webapp\WEB-INF\{servlet-name}-servlet.xml。

      如本例:servlet-name=database,则文件名为database-servlet.xml,所有会查找${project.dir}\src\main\webapp\WEB-INF\database-servlet.xml

具体查看文档https://docs.spring.io/spring/docs/3.2.18.RELEASE/spring-framework-reference/htmlsingle/#mvc-introduction中17.2

3、这样配置最终spring会产生两个容器,ContextLoaderListener会生成容器A,DispatcherServlet会生成容器B,容器B的父容器是容器A,容器A的父容器为null

4、容器B会继承容器A的部分功能(其实这样做主要是为了防止单例bean的重复注入和容器B可以获取到容器A的bean,这样进行bean共享,但是容器A获取不到容器B的bean)

5、这样的配置spring mvc就有了,不需要在database-servlet.xml中配置<mvc:annotation-driven/>

问题:

如果在applicationContext-service-database.xml配置<mvc:annotation-driven/>,而在database-servlet.xml没有配置<mvc:annotation-driven/>,这样就会导致spring mvc功能失效,访问地址会404

原因:

因为如果在applicationContext-service-database.xml配置<mvc:annotation-driven/>,就会导致ContextLoaderListener加入容器A提前把spring mvc加载到容器A中,但是此时容器A,并没有database-servlet.xml中对应需要向外暴露的接口、url或controller。当DispatcherServlet加载容器B的时候发现,它的父容器A中已经存在spring mvc的相关功能,就不会重复加载,所以就会导致spring mvc的功能失效

关键代码:

org.springframework.web.servlet.DispatcherServlet
private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;
 
   if (this.detectAllHandlerMappings) {
      // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
      // BeanFactoryUtils.beansOfTypeIncludingAncestors 会去获取容器B和它的父容器A中的HandlerMapping的接口实现,如果ContextLoaderListener已经配置了<mvc:annotation-driven/>
      // ,则直接使用ContextLoaderListener加载的HandlerMapping
      Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
         // We keep HandlerMappings in sorted order.
         OrderComparator.sort(this.handlerMappings);
      }
   }
   else {
      try {
         HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
         this.handlerMappings = Collections.singletonList(hm);
      }
      catch (NoSuchBeanDefinitionException ex) {
         // Ignore, we'll add a default HandlerMapping later.
      }
   }
 
   // Ensure we have at least one HandlerMapping, by registering
   // a default HandlerMapping if no other mappings are found.
   // 如果在任何配置文件中都没有配置spring mvc 功能,如:<mvc:annotation-driven/>,则会使用默认的HandlerMapping,默认的HandlerMapping是
   //org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping, org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
   // 相关默认配置请查看org\springframework\web\servlet\DispatcherServlet.properties
   if (this.handlerMappings == null) {
      this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
      if (logger.isDebugEnabled()) {
         logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
      }
   }
}

这只是spring mvc 其中一个功能,其他相关功能请查看DispatcherServletinit*方法

解决办法:
不要在ContextLoaderListener加载任何与spring mvc相关的功能

最后
ContextLoaderListener就是为了加载公用的类而存在的
一般ContextLoaderListener只有一个,而DispatcherServlet可以有多个,每个DispatcherServlet都会默认的以ContextLoaderListener加载的容器为父容器。

所以请按照spring 的套路来,减少不必须的问题

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!