Spring-SpringMVC父子容器
前言
Spring&SpringMVC作為bean管理容器和MVC預設框架,是大多數web應用都會選擇的方案。在其使用過程中,儘管基於xml的配置bean管理的方式依然存在,但在很多情況下已經採用的強大的註解功能將其替代。實際專案中,Spring和SpringMVC同時配置,以及xml配置bean和註解的混合使用,會造成諸如bean重複載入、多次例項化、無法自動注入、配置不生效等奇怪的異常現象。其實,以上問題的大多數原因還是出在Spring容器的理解與使用上。
容器
Spring整體框架核心概念中,容器是核心思想,但在一個專案,容器不一定只有一個,Spring中可以包括多個容器,且容器間存在上下層框架。最常見的使用場景就是同時使用Spring和SpringMVC兩個框架,Srping為父(根)容器,SpringMVC作為子容器。通常的使用過程中,Spring父容器對SpringMVC子容器中的bean是不可見的,而子容器對父容器的中bean卻是可見的,這是這兩種容器的預設規則。但是:
子容器對父容器中內容可見,不是預設規則
載入過程
web容器
對於一個web應用,需要將其部署在web容器中,web容器為其提供一個全域性的ServletContext,並作為之後Spring容器提供宿主環境。
Spring根容器
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
1.web.xml中的contextLoaderListener在web容器啟動時,會監聽到web容器的初始化事件,其contextInitialized方法會被呼叫,在這個方法中,spring會初始化一個啟動上下文作為根上下文,即WebApplicationContext。WebApplicationContext只是介面類,其實際的實現類是XmlWebApplicationContext。
2.此上下文即作為Spring根容器,其對應的bean定義的配置由web.xml中的context-param中的contextConfigLocation指定。根容器初始化完畢後,Spring以
WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE
為屬性Key,將其儲存到ServletContext中,便於獲取。
SpringMVC子容器
<servlet>
<servlet-name>XXXX</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>
1.contextLoaderListener監聽器初始化完畢後,開始初始化web.xml中配置的Servlet,servlet可以配置多個,載入順序按照load-on-startup來執行。以最常見的DispatcherServlet為例,該servlet實際上是一個標準的前端控制器,用以轉發、匹配、處理每個servlet請求。DispatcherServlet上下文在初始化的時候會建立自己的上下文,用以持有SpringMVC相關的bean。
2.之後先通過
WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE
先從ServletContext中獲取之前的根上下文(即WebApplicationContext)作為自己上下文的父上下文,然後建立DispatcherServlet自己的上下文。這個DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,內容包括初始化處理器對映、檢視解析等。該servlet自己持有的上下文預設實現類也是WebApplicationContext。
3.初始化完畢後,spring以與servlet的名字相關(此處並非簡單以servlet名為Key,通過轉換碼生存)的屬性為屬性Key,也將其存到ServletContext中,以便後續使用。這樣每個servlet就持有自己的上下文,即擁有自己獨立的bean空間,同時各個servlet共享相同的根上下文中持有的bean。
當然,在根容器建立與子容器建立之間,還會建立監聽器、過濾器等,完整的載入順序為:
ServletContext -> context-param -> listener-> filter -> servlet
由以上過程,即確定了一個bean的使用範圍(該使用範圍,是比bean的scope定義的singleton、prototype、request、session、global-session的五種作用域更上層的內容)。
容器佈局
容器佈局的根本是確定bean的使用範圍。就Spring與SpringMVC佈局,也大致分為了兩種方向:
傳統型
父容器:儲存資料來源、服務層、DAO層、事務的bean。
子容器:儲存MVC相關的controller的bean。
概述:事務控制在服務層。由於父容器不能訪問文容器中內容,事務的bean在父容器中,無法訪問子容器中內容,就無法對子容器中controller進行AOP(事務),不過做為傳統型方案,也沒有必要這要做。
激進型
父容器:不關我事~
子容器:管理所有bean。
概述:不使用listener監聽器來載入spring的配置檔案,只使用DispatcherServlet來載入Spring的配置,不使用父容器,只使用一個DispatcherServlet,拋棄層次概念。
場景:在增刪改查為主業務的系統裡,Dao層介面,Dao層實現類,Service層介面,Service層實現類,Action父類,Action。再加上眾多的O(vo\po\bo)和jsp頁面,在滿足分層的前提下,做一些相對較小功能時會變得非常冗餘,所以“激進型”方案就出現了,沒有介面、沒有Service層、可以沒有眾多的O(vo\po\bo),所有事務控制上升到controller層。
關於佈局選擇嗎,引用一句很合景的總結:
大專案求穩,小專案求快。