Spring和SpringMVC父子容器关系初窥

1、背景

  最近因为项目的包扫描出现了问题,在解决问题的过程当中,偶然发现了Spring和SpringMVC是有父子容器关系的,并且正是由于这个才每每会出现包扫描的问题,咱们在此来分析和理解Spring和SpringMVC的父子容器关系而且给出Spring和SpringMVC配置文件中包扫描的官方推荐方式。html

2、概念理解和知识铺垫

  在Spring总体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的,而在一个项目中,容器不必定只有一个,Spring中能够包括多个容器,并且容器有上下层关系,目前最多见的一种场景就是在一个项目中引入Spring和SpringMVC这两个框架,那么它其实就是两个容器,Spring是父容器,SpringMVC是其子容器,而且在Spring父容器中注册的Bean对于SpringMVC容器中是可见的,而在SpringMVC容器中注册的Bean对于Spring父容器中是不可见的,也就是子容器能够看见父容器中的注册的Bean,反之就不行。web

  咱们可使用统一的以下注解配置来对Bean进行批量注册,而不须要再给每一个Bean单独使用xml的方式进行配置。spring

<context:component-scan base-package="com.hafiz.www" />

  从Spring提供的参考手册中咱们得知该配置的功能是扫描配置的base-package包下的全部使用了@Component注解的类,而且将它们自动注册到容器中,同时也扫描@Controller,@Service,@Respository这三个注解,由于他们是继承自@Component。express

  在项目中咱们常常见到还有以下这个配置,其实有了上面的配置,这个是能够省略掉的,由于上面的配置会默认打开如下配置。如下配置会默认声明了@Required、@Autowired、 @PostConstruct、@PersistenceContext、@Resource、@PreDestroy等注解。json

<context:annotation-config/>

  另外,还有一个和SpringMVC相关以下配置,通过验证,这个是SpringMVC必需要配置的,由于它声明了@RequestMapping、@RequestBody、@ResponseBody等。而且,该配置默认加载不少的参数绑定方法,好比json转换解析器等。mvc

<mvc:annotation-driven />

而上面这句配置spring3.1以前的版本和如下配置方式等价app

<!--配置注解控制器映射器,它是SpringMVC中用来将Request请求URL到映射到具体Controller-->
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<!--配置注解控制器映射器,它是SpringMVC中用来将具体请求映射到具体方法-->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

spring3.1以后的版本和如下配置方式等价框架

<!--配置注解控制器映射器,它是SpringMVC中用来将Request请求URL到映射到具体Controller-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--配置注解控制器映射器,它是SpringMVC中用来将具体请求映射到具体方法-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

3、具体场景分析

  下面让咱们来详细扒一扒Spring与SpringMVC的容器冲突的缘由到底在那里?post

  咱们共有Spring和SpringMVC两个容器,它们的配置文件分别为applicationContext.xml和applicationContext-MVC.xml。ui

  1.在applicationContext.xml中配置了<context:component-scan base-package=“com.hafiz.www" />,负责全部须要注册的Bean的扫描和注册工做。

  2.在applicationContext-MVC.xml中配置<mvc:annotation-driven />,负责SpringMVC相关注解的使用。

  3.启动项目咱们发现SpringMVC没法进行跳转,将log的日志打印级别设置为DEBUG进行调试,发现SpringMVC容器中的请求好像没有映射到具体controller中。

  4.在applicationContext-MVC.xml中配置<context:component-scan base-package=“com.hafiz.www" />,重启后,验证成功,springMVC跳转有效。

  下面咱们来查看具体缘由,翻看源码,从SpringMVC的DispatcherServlet开始往下找,咱们发现SpringMVC初始化时,会寻找SpringMVC容器中的全部使用了@Controller注解的Bean,来肯定其是不是一个handler。1,2两步的配置使得当前springMVC容器中并无注册带有@Controller注解的Bean,而是把全部带有@Controller注解的Bean都注册在Spring这个父容器中了,因此springMVC找不处处理器,不能进行跳转。核心源码以下:

protected void initHandlerMethods() {
  if (logger.isDebugEnabled()) {
    logger.debug("Looking for request mappings in application context: " + getApplicationContext());
  }
  String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
        BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
       getApplicationContext().getBeanNamesForType(Object.class));
  for (String beanName : beanNames) {
    if (isHandler(getApplicationContext().getType(beanName))){
      detectHandlerMethods(beanName);
    }
  }
  handlerMethodsInitialized(getHandlerMethods());
}

在方法isHandler中会判断当前bean的注解是不是controller,源码以下:

protected boolean isHandler(Class<?> beanType) {
  return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
}

而在第4步配置中,SpringMVC容器中也注册了全部带有@Controller注解的Bean,故SpringMVC能找处处理器进行处理,从而正常跳转。

咱们找到了出现不能正确跳转的缘由,那么它的解决办法是什么呢?

  咱们注意到在initHandlerMethods()方法中,detectHandlerMethodsInAncestorContexts这个Switch,它主要控制获取哪些容器中的bean以及是否包括父容器,默认是不包括的。因此解决办法就是在springMVC的配置文件中配置HandlerMapping的detectHandlerMethodsInAncestorContexts属性为true便可(这里须要根据具体项目看使用的是哪一种HandlerMapping),让它检测父容器的bean。以下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
   <property name="detectHandlerMethodsInAncestorContexts">
       <value>true</value>
   </property>
</bean>

但在实际工程中会包括不少配置,咱们按照官方推荐根据不一样的业务模块来划分不一样容器中注册不一样类型的Bean:Spring父容器负责全部其余非@Controller注解的Bean的注册,而SpringMVC只负责@Controller注解的Bean的注册,使得他们各负其责、明确边界。配置方式以下

  1.在applicationContext.xml中配置:

<!-- Spring容器中注册非@controller注解的Bean -->
<context:component-scan base-package="com.hafiz.www"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>

  2.applicationContext-MVC.xml中配置

<!-- SpringMVC容器中只注册带有@controller注解的Bean -->
<context:component-scan base-package="com.hafiz.www" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan>

关于use-default-filters="false"的做用,请参见另外一篇博客:context:component-scan标签的use-default-filters属性的做用以及原理分析

3、总结

  这样咱们在清楚了spring和springMVC的父子容器关系、以及扫描注册的原理之后,根据官方建议咱们就能够很好把不一样类型的Bean分配到不一样的容器中进行管理。再出现Bean找不到或者SpringMVC不能跳转以及事务的配置失效的问题,咱们就能够很快的定位以及解决问题了。很开心,有木有~

相关文章
相关标签/搜索