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的方式进行配置。
<context:component-scan base-package="com.hafiz.www" />
从Spring提供的参考手册中我们得知该配置的功能是扫描配置的base-package包下的所有使用了@Component注解的类,并且将它们自动注册到容器中,同时也扫描@Controller,@Service,@Respository这三个注解,因为他们是继承自@Component。
在项目中我们经常见到还有如下这个配置,其实有了上面的配置,这个是可以省略掉的,因为上面的配置会默认打开以下配置。以下配置会默认声明了@Required、@Autowired、 @PostConstruct、@PersistenceContext、@Resource、@PreDestroy等注解。
<context:annotation-config/>
另外,还有一个和SpringMVC相关如下配置,经过验证,这个是SpringMVC必须要配置的,因为它声明了@RequestMapping、@RequestBody、@ResponseBody等。并且,该配置默认加载很多的参数绑定方法,比如json转换解析器等。
<mvc:annotation-driven />
而上面这句配置spring3.1之前的版本和以下配置方式等价
<!--配置注解控制器映射器,它是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"/>
三、具体场景分析
下面让我们来详细扒一扒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找不到处理器,不能进行跳转。核心源码如下:
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属性的作用以及原理分析
三、总结
这样我们在清楚了spring和springMVC的父子容器关系、以及扫描注册的原理以后,根据官方建议我们就可以很好把不同类型的Bean分配到不同的容器中进行管理。再出现Bean找不到或者SpringMVC不能跳转以及事务的配置失效的问题,我们就可以很快的定位以及解决问题了。很开心,有木有~
Spring和SpringMVC父子容器关系初窥的更多相关文章
- spring和springmvc父子容器关系
一般来说,我们在整合spring和SpringMVC这两个框架中,web.xml会这样写到: <!-- 加载spring容器 --> <!-- 初始化加载application.xm ...
- Spring和springMVC父子容器的关系
部分转载自:https://www.cnblogs.com/ljdblog/p/7461854.html springMVC容器和Spring容器 为什么一定要在web.xml中配置spring的li ...
- (转载)Spring与SpringMVC父子容器的关系与初始化
转自 https://blog.csdn.net/dhaiuda/article/details/80026354 Spring和SpringMVC的容器具有父子关系,Spring容器为父容器,Spr ...
- spring与springmvc父子容器
转载地址:http://www.tianshouzhi.com/api/tutorials/spring 1.spring和springmvc父子容器概念介绍 在spring和springmvc进行整 ...
- 1、spring与springmvc父子容器
转载于http://www.tianshouzhi.com/api/tutorials/spring 1.0 spring与springmvc父子容器 1.spring和springmvc父子容器概念 ...
- Spring与SpringMVC的容器关系分析
Spring和SpringMVC作为Bean管理容器和MVC层的默认框架,已被众多WEB应用采用,而实际使用时,由于有了强大的注解功能,很多基于XML的配置方式已经被替代,但是在实际项目中,同时配置S ...
- 面试高频题:说一说对Spring和SpringMvc父子容器的理解?
引言 以前写了几篇关于SpringBoot的文章<面试高频题:springBoot自动装配的原理你能说出来吗>.<保姆级教程,手把手教你实现一个SpringBoot的starter& ...
- Spring和springmvc父子容器注解扫描问题详解
一.Spring容器和springmvc容器的关系如下图所示: Spring和springmvc和作为两个独立的容器,会把扫描到的注解对象分别放到两个不同的容器中, Springmvc容器是spr ...
- 转:spring的启动过程-spring和springMVC父子容器的原理
要想很好理解这三个上下文的关系,需要先熟悉spring是怎样在web容器中启动起来的.spring的启动过程其实就是其IoC容器的启动过程,对于web程序,IoC容器启动过程即是建立上下文的过程. s ...
随机推荐
- UOJ58 【WC2013】糖果公园
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...
- PHP 信号管理
.note-content { font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", STHeit ...
- 个人作业——week3
1.软件工程师的成长 拜读了多位优秀前辈的文章后,感觉十分复杂,虽然说不上是醍醐灌顶的那种大彻大悟,但是确实的感觉到自己内心中某个部分被真切的感触到了.推荐的文章语言都比较平易近人,感觉就像是和多年未 ...
- 利用BitMap进行大数据排序去重
1.问题 问题提出: M(如10亿)个int整数,只有其中N个数重复出现过,读取到内存中并将重复的整数删除. 2.解决方案 问题分析: 我们肯定会先想到在计算机内存中开辟M个int整型数据数组,来on ...
- php ob_flush 和flush
“ob_flush()和flush()的区别.前者是把数据从PHP的缓冲中释放出来,后者是把不在缓冲中的或者说是被释放出来的数据发送到浏览器.所以当缓冲存在的时候,我们必须ob_flush()和flu ...
- 学习CodeIgniter框架之旅(二)继承自定义类
在很多情况下,框架类并不能满足项目的需求,这时候需要程序要自定义一些类,比如说基类等等,对比了TP框架,CI框架目前好像还没加入命名空间,这点TP做得比较好,不用特殊的处理就可以随便继承自定义的类,只 ...
- 现代软件工程作业 第二章 学习github笔记
在网上大量资料的辅助下,学习了github的基本使用方法,尝试了一些常见的命令.为了便于记忆总结了自己的学习内容. 1.首先需要在github的官网上注册一个帐号,并新建一个repository,选这 ...
- REDHAT一总复习1 cups 打印服务配置
停止cups服务 $ sudo systemctl stop cups $ sudo systemctl status cups 配置cups服务,使其不在系统启动时启动 $ sudo systemc ...
- REDHAT一总复习1更改系统文档文件
十台linux系统需要更改文档.请在server上执行以下任务: .在server计算机上,以student用户在/home/student目录中创建空文件,并将文件取名system_changes- ...
- Load Test Analyzer Overview
reference url: https://msdn.microsoft.com/en-us/library/ms404677.aspx