原文地址:https://segmentfault.com/a/1190000012972619java
你们好,我是猪弟,猪在我心中历来不是蠢的代名词,而是懒的代名词,本次准备记录一个在开发测试过程当中遇到的问题,跟踪了三天spring和第三方RPC组件的源码,最终发现了问题是由于第三方组件没有处理好而父子容器致使的,还有一个因素是spring注解扫描重叠。web
Spring版本:4.3.13.RELEASEJDK版本:1.7_u25 64位spring
在SpringMVC
的配置中为了防止Spring重复建立同一个类的实例,通常会用到<context:component-scan>
的两个子标签<context:include-filter>&&<context:exclude-filter>
。express
但它使用的时候表现的效果并非和语义上的彻底一致,如今来看一下其中的坑:编程
在不少配置中通常都会把spring-config.xml
和spring-mvc.xml
进行分开配置,这种配置能够他们保证各司其职,在web.xml的通常配置中spring-mvc.xml
实例建立初始化是以DispatchServlet
为入口,而spring-config.xml
实例建立初始化是以ContextLoadListener
为入口的,容器的加载顺序: listener -> filter -> servlet
,因此spring容器先初始化,springmvc容器后初始化 。segmentfault
<!--spring 入口--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring-config.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--spring mvc 入口--> <servlet> <servlet-name>blog-spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring-mvc.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>blog-spring-mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
若是在spring-mvc.xml
中配置扫描的包和spring-config.xml
中的发生重叠,那么会致使一个bean被建立两次,并且在spring
中是存在父子容器的,spring
容器是父容器,springmvc
是子容器,springmvc
建立的实例放在子容器中,spring
建立的实例放在父容器中。spring-mvc
其实这同一个类的两个实例是不一样的,springmvc
建立实例默认对象不实现接口(你们都知道Controller是不用实现接口的),因此springmvc建立的实例是直接使用目标类的构造器来实例化的,而不是代理对象,即便一个类实现了接口,但若是该类是由springmvc实例化,那么springmvc也会直接使用该类的构造器直接建立一个对象(怎么去证实呢,你能够写一个定时任务,在定时任务中注入Controller的实例,而后debug查看实例对象的地址,若是是代理对象在地址上都会有一个$Proxy的标记,不然就不是代理对象),因此在controller层使用AOP时多数采用的是CGLIB子类代理。mvc
Spring建立实例会判断目标类是否实现了接口,若是没实现接口那么就直接采用目标类构造器建立,像通常的service和dao都会采用接口方式编程,对于接口方式编程的类,spring建立的实例都是代理对象(这一点能够用debug的方式查看controller类中注入的service实例对象地址,他们都带有一个$Proxy的标记,很容易就能看出都是代理对象)。app
那么为了防止重叠咱们要把重叠的部分去掉,如今有下面的一个需求:测试
在spring-mvc.xml
中只对工程中全部用@Controller
注解的类进行扫描建立实例。
在spring-config.xml
中要对工程中全部的非@Controller
注解的类进行扫描建立实例。
如今给定一个项目的包结构:
xin.sun.blog.controlller
xin.sun.blog.service
(1)在spring-mvc.xml
中有如下配置:
<!-- 只扫描 @Controller注解--> <context:component-scanbase-package="xin.sun.blog.controlller"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
能够看出要把最后的包写上,不能包含子包,因此不能写成: base-package="xin.sun.blog"
。若是这样写,对于 include-filter
标签来说它会扫描基包下面全部spring注解的类,而不是仅仅扫描 @Controller
。这点须要很是的注意,这通常会致使一个常见的错误,那就是事务不起做用,补救的方法是添加 use-default-filters="false"
。
(2)在spring-config.xml
中有以下配置:
<!-- 配置扫描注解,不扫描 @Controller注解--> <context:component-scan base-package="xin.sun.blog"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
能够看到,他是要扫描xin.sun.blog
包和子包下的全部spring注解的类,可是不包含@Controller
注解的类。对于exculude-filter不存在包不精确致使都进行扫描的问题。
那么还有一个问题:当扫描的包不当心重叠了,致使类在父子容器各实例化了一遍,在 @Autowire
的时候会注入哪一个容器中的对象呢?看一个Controller类,代码以下:
@Controller public class MyController{ @Autowired private IValidService validService; //其余代码省略 }
答案是:Spring为了保证注入类的一致性,采用了双亲委托的机制,若是父容器中存在该类的实例那么优先使用父容器中的实例,若是父容器中没有该实例才会用子容器中的实例