你們好,我是磊叔的豬弟,豬在我心中歷來不是蠢的代名詞,而是懶的代名詞,本次準備記錄一個在開發測試過程當中遇到的問題,跟蹤了三天spring和第三方RPC組件的源碼,最終發現了問題是由於第三方組件沒有處理好而父子容器致使的,還有一個因素是spring註解掃描重疊。java
Spring版本:4.3.13.RELEASEweb
IDE工具:IDEA 2017.2.6spring
JDK版本:1.7_u25 64位express
在SpringMVC
的配置中爲了防止Spring重複建立同一個類的實例,通常會用到<context:component-scan>
的兩個子標籤<context:include-filter>&&<context:exclude-filter>
。編程
但它使用的時候表現的效果並非和語義上的徹底一致,如今來看一下其中的坑:spring-mvc
在不少配置中通常都會把spring-config.xml
和spring-mvc.xml
進行分開配置,這種配置能夠他們保證各司其職,在web.xml的通常配置中spring-mvc.xml
實例建立初始化是以DispatchServlet
爲入口,而spring-config.xml
實例建立初始化是以ContextLoadListener
爲入口的,容器的加載順序:listener -> filter -> servlet
,因此spring容器先初始化,springmvc容器後初始化 。mvc
<!--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
建立的實例放在父容器中。app
其實這同一個類的兩個實例是不一樣的,springmvc
建立實例默認對象不實現接口(你們都知道Controller是不用實現接口的),因此springmvc建立的實例是直接使用目標類的構造器來實例化的,而不是代理對象,即便一個類實現了接口,但若是該類是由springmvc實例化,那麼springmvc也會直接使用該類的構造器直接建立一個對象(怎麼去證實呢,你能夠寫一個定時任務,在定時任務中注入Controller的實例,而後debug查看實例對象的地址,若是是代理對象在地址上都會有一個$Proxy的標記,不然就不是代理對象),因此在controller層使用AOP時多數採用的是CGLIB子類代理。工具
Spring建立實例會判斷目標類是否實現了接口,若是沒實現接口那麼就直接採用目標類構造器建立,像通常的service和dao都會採用接口方式編程,對於接口方式編程的類,spring建立的實例都是代理對象(這一點能夠用debug的方式查看controller類中注入的service實例對象地址,他們都帶有一個$Proxy的標記,很容易就能看出都是代理對象)。測試
那麼爲了防止重疊咱們要把重疊的部分去掉,如今有下面的一個需求:
在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爲了保證注入類的一致性,採用了雙親委託的機制,若是父容器中存在該類的實例那麼優先使用父容器中的實例,若是父容器中沒有該實例纔會用子容器中的實例