背景:
最近在搭建新工程的时候发现有些Spring的配置不是很了解,比如Spring 配置里面明明配置了component-scan,为啥Spring MVC配置文件还需要配置一下,这样岂不是多此一举?由于以前基本是在现有的工程上直接开发或者别的工程的配置文件直接拷贝过来,所以也没太关注这个问题。出于好奇,谷歌了一下发现原来这个里面大有学问呢,详情请见下文。正常代码如下:
Xml代码
1. <!-- spring 配置文件-->
2. <context:component-scan base-package="com.xxx.xxx.account.front">
3. <context:exclude-filter type="annotation"
4. expressinotallow="org.springframework.stereotype.Controller" />
5. </context:component-scan>
6.
7. <!-- spring mvc -->
8. <context:component-scan base-package="com.xxx.xxx.account.front.web" use-default-filters="false">
9. <context:include-filter type="annotation"
10. expressinotallow="org.springframework.stereotype.Controller" />
11. </context:component-scan>
测试bean
Java代码
1. @Service
2. public class TestService implements InitializingBean {
3.
4. @Autowired
5. private PersonalAddressAjaxController personalAddressAjaxController;
6.
7. @Override
8. public void afterPropertiesSet() throws Exception {
9. "--------------------------------");
10. }
11. }
原理:
原来Spring 是父容器, Spring MVC是子容器, 子容器可以访问父容器的bean,父容器不能访问子容器的bean。
具体参照:
Spring和SpringMVC父子容器关系初窥
测试一: Spring加载全部bean,MVC加载Controller
Xml代码
1. <!-- spring 配置文件-->
2. <context:component-scan base-package="com.xxx.xxx.account.front">
3. </context:component-scan>
4.
5. <!-- spring mvc -->
6. <context:component-scan base-package="com.xxx.xxx.account.front.web" use-default-filters="false">
7. <context:include-filter type="annotation"
8. expressinotallow="org.springframework.stereotype.Controller" />
9. </context:component-scan>
测试结果:TestService通过,界面显示正常。
原因:父容器加载了全部bean,所以Service 能访问到Controller。MVC容器默认查找当前容器,能查到有转发的Controller规则所以界面正常跳转。
测试二:Spring加载全部Bean,MVC容器啥也不加载
Xml代码
1. <!-- spring 配置文件-->
2. <context:component-scan base-package="com.xxx.xxx.account.front">
3. </context:component-scan>
4.
5. <!-- spring mvc -->
6. 无
测试结果:TestService通过,界面显示404。
原因:父容器加载了全部bean,所以Service 能访问到Controller。MVC容器默认查找当前容器的Controller,找不到所以界面出现404。
测试三:Spring加载所有除了Controller的bean,MVC只加载Controller
Xml代码
1. <!-- spring 配置文件-->
2. <context:component-scan base-package="com.xxx.xxx.account.front">
3. <context:exclude-filter type="annotation"
4. expressinotallow="org.springframework.stereotype.Controller" />
5. </context:component-scan>
6.
7. <!-- spring mvc -->
8. <context:component-scan base-package="com.xxx.xxx.account.front.web" use-default-filters="false">
9. <context:include-filter type="annotation"
10. expressinotallow="org.springframework.stereotype.Controller" />
11. </context:component-scan>
测试结果:TestService初始化失败,如果注释掉该bean,界面正常。
原因:父容器不能访问子容器的bean。
测试四:Spring不加载bean,MVC加载所有的bean
Xml代码
1. <!-- spring 配置文件-->
2. 无
3.
4. <!-- spring mvc -->
5. <context:component-scan base-package="com.xxx.xxx.account.front.web" use-default-filters="true">
6. </context:component-scan>
测试结果:TestService通过,界面正常。
原因:因为所有的bean都在子容器中,也能查到当前容器中的Controller,所以没啥问题。
疑问一: 单例的bean在父子容器中存在一个实例还是两个实例?
答:初始化两次,Spring 容器先初始化bean,MVC容器再初始化bean,所以应该是两个bean。
疑问二:为啥不把所有bean 都在子容器中扫描?
答: 网上很多文章说子容器不支持AOP,其实这是不对的。因为正常会有AOP的相关配置都在Spring容器中配置,如果都迁移到MVC配置文件,则所有bean都在子容器中,相当于只有一个容器了,所以也就实现了AOP。缺点是不利于扩展。