Spring 和 SpringMVC 父子容器关系初窥
一、背景
最近由于项目的包扫描出现了问题,在解决问题的过程中,偶然发现了 Spring 和 SpringMVC 是有父子容器关系的,而且正是因为这个才往往会出现包扫描的问题,我们在此来分析和理解 Spring 和 SpringMVC 的父子容器关系并且给出 Spring 和 SpringMVC 配置文件中包扫描的官方推荐方式。
二、概念理解和知识铺垫
在 Spring 整体框架的核心概念中,容器是核心思想,就是用来管理 Bean 的整个生命周期的,而在一个项目中,容器不一定只有一个,Spring 中可以包括多个容器,而且容器有上下层关系,目前最常见的一种场景就是在一个项目中引入 Spring 和 SpringMVC 这两个框架,那么它其实就是两个容器,Spring 是父容器,SpringMVC 是其子容器,并且在 Spring 父容器中注册的 Bean 对于 SpringMVC 容器中是可见的,而在 SpringMVC 容器中注册的 Bean 对于 Spring 父容器中是不可见的,也就是子容器可以看见父容器中的注册的 Bean,反之就不行。
我们可以使用统一的如下注解配置来对 Bean 进行批量注册,而不需要再给每个 Bean 单独使用 xml 的方式进行配置。
[XML] 纯文本查看 复制代码
?
1
<context:component-scan base-package=”com.hafiz.www” />
从 Spring 提供的参考手册中我们得知该配置的功能是扫描配置的 base-package 包下的所有使用了 @Component 注解的类,并且将它们自动注册到容器中,同时也扫描 @Controller,@Service,@Respository 这三个注解,因为他们是继承自 @Component。
在项目中我们经常见到还有如下这个配置,其实有了上面的配置,这个是可以省略掉的,因为上面的配置会默认打开以下配置。以下配置会默认声明了 @Required、@Autowired、@PostConstruct、@PersistenceContext、@Resource、@PreDestroy 等注解。
[XML] 纯文本查看 复制代码
?
1
<context:annotation-config/>
另外,还有一个和 SpringMVC 相关如下配置,经过验证,这个是 SpringMVC 必须要配置的,因为它声明了 @RequestMapping、@RequestBody、@ResponseBody 等。并且,该配置默认加载很多的参数绑定方法,比如 json 转换解析器等。
[XML] 纯文本查看 复制代码
?
1
<mvc:annotation-driven />
而上面这句配置 spring3.1 之前的版本和以下配置方式等价
[XML] 纯文本查看 复制代码
?
<!– 配置注解控制器映射器, 它是 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 之后的版本和以下配置方式等价
[AppleScript] 纯文本查看 复制代码
?
<!– 配置注解控制器映射器, 它是 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”/>
三、具体场景分析
下面让我们来详细扒一扒 Spring 与 SpringMVC 的容器冲突的原因到底在那里?
我们共有 Spring 和 SpringMVC 两个容器,它们的配置文件分别为 applicationContext.xml 和 applicationContext-MVC.xml。
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 找不到处理器,不能进行跳转。核心源码如下:
[Java] 纯文本查看 复制代码
?
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,源码如下:
[AppleScript] 纯文本查看 复制代码
?
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。如下:
[XML] 纯文本查看 复制代码
?
<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 中配置:
[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 中配置
[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 属性的作用以及原理分析
播客地址:http://www.cnblogs.com/hafiz/…
三、总结
这样我们在清楚了 spring 和 springMVC 的父子容器关系、以及扫描注册的原理以后,根据官方建议我们就可以很好把不同类型的 Bean 分配到不同的容器中进行管理。再出现 Bean 找不到或者 SpringMVC 不能跳转以及事务的配置失效的问题,我们就可以很快的定位以及解决问题了。很开心,有木有~
【转载,仅作分享,侵删】作者:阿豪聊干货
原文:https://www.cnblogs.com/hafiz…
版权声明:本文为博主原创文章,转载请附上博文链接!