【SpringMVC】&&爬坑

  你們好,我是磊叔的豬弟,豬在我心中歷來不是蠢的代名詞,而是懶的代名詞,本次準備記錄一個在開發測試過程當中遇到的問題,跟蹤了三天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.xmlspring-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爲了保證注入類的一致性,採用了雙親委託的機制,若是父容器中存在該類的實例那麼優先使用父容器中的實例,若是父容器中沒有該實例纔會用子容器中的實例

相關文章
相關標籤/搜索