
可能有人會覺得,既然spring是一個IOC容器或者說是一個bean的容器,那么應該從spring-beans看起,先了解spring是如何從xml文件配置獲取需要創建的bean的信息,但是這里有個問題就是雖然知道怎么遍歷初始化,但是不知道哪里用到或者說哪里讓這些初始化開始,而且像BeanFactory,FactoryBean,Environment,PropertySource等接口還是比較抽象的,比較難看懂,所以很容易讓人感覺枯燥,然后就放棄了。
我們可以換個思路,從能接觸到的角度開始,即我們通常會使用spring-mvc來進行web開發,如@Controller,@RequestMapping都是再熟悉不過的了。如果搭過spring-mvc項目都知道,通常需要在web.xml文件中,配置一個ContextLoaderListener,contextConfigLocation,DispatcherServlet,可能很多人都是從網上copy了一份配置過來或者知道contextConfigLocation是指定spring配置文件的位置,DispatcherServlet是接收所有請求的前端控制器,需要指定攔截路由:“/”,從而攔截所有URL中帶“/”的請求,但是在spring源碼中是怎么使用這些組件的呢?以及怎么配置了一個@Controller,@RequestMapping中指定了一個url,就可以訪問了呢?還有就是通常我們的web項目都會部署在web容器,如tomcat當中,那么tomcat和spring有啥關系呢?所以我們可以帶着這些問題去查看spring源碼找到答案。
所以我推薦是從spring-mvc開始看spring源碼,因為這個是我們使用得比較多,比較容易理解的一個模塊,然后一層一層往上剝,找到與spring-context,spring-beans,spring-aop等的關系。如果真的對JavaWeb開發,Java EE很感興趣,或者更容易讀懂spring的源碼,可以先看servlet規范和Tomcat的設計與Tomcat的請求處理工作流。我目前也在結合這兩個方面看,也可以看下我的Tomcat源碼分析系列。
在web容器啟動的時候,會初始化web應用,即創建ServletContext對象,加載解析web.xml文件,獲取該應用的Filters,Listener,Servlet等組件的配置並創建對象實例,作為ServletContext的屬性,保存在ServletContext當中。之后web容器接收到客戶端請求時,則會根據請求信息,匹配到處理這個請求的Servlet,同時在交給servlet處理之前,會先使用應用配置的Filters對這個請求先進行過濾,最后才交給servlet處理。
了解web容器啟動,之后接受客戶端請求這些知識有啥用處呢?這里我們需要回過頭來看我們的spring項目。我們在日常開發中,直接接觸的是spring相關的組件,然后打成war包,放到web容器中,如拷貝到tomcat的webapp目錄,並不會直接和web容器打交道。經過以上的分析,其實一個spring項目就是對應web容器里的一個ServletContext,所以在ServletContext對象的創建和初始化的時候,就需要一種機制來觸發spring相關組件的創建和初始化,如包含@Controller和@RequestMapping注解的類和方法,這樣才能處理請求。
其中一個重要的生命周期監聽器是ServletContextListener。web容器在創建和初始化ServletContext的時候,會產生一個ServletContextEvent事件,其中ServletContextEvent包含該ServletContext的引用。然后交給在web.xml中配置的,注冊到這個ServletContext的監聽器ServletContextListener。ServletContextListener在其contextInitialized方法中定義處理邏輯,接口定義如下:
/** * Implementations of this interface receive notifications about changes to the * servlet context of the web application they are part of. To receive * notification events, the implementation class must be configured in the * deployment descriptor for the web application. * * @see ServletContextEvent * @since v 2.3 */ public interface ServletContextListener extends EventListener { /** ** Notification that the web application initialization process is starting. * All ServletContextListeners are notified of context initialization before * any filter or servlet in the web application is initialized. * @param sce Information about the ServletContext that was initialized */ public void contextInitialized(ServletContextEvent sce); /** ** Notification that the servlet context is about to be shut down. All * servlets and filters have been destroy()ed before any * ServletContextListeners are notified of context destruction. * @param sce Information about the ServletContext that was destroyed */ public void contextDestroyed(ServletContextEvent sce); }
從contextInitialized的注釋可知:通知所有的ServletContextListeners,當前的web應用正在啟動,而且這些ServletContextListeners是在Filters和Servlets創建之前接收到通知的。所以在這個時候,web應用還不能接收請求,故可以在這里完成底層處理請求的組件的加載,這樣等之后接收請求的Filters和Servlets創建時,則可以使用這些創建好的組件了。spring相關的bean就是這里所說的底層處理請求的組件,如數據庫連接池,數據庫事務管理器等。
ContextLoaderListener:spring-web包的ContextLoaderListener就是一個ServletContextListener的實現類。ContextLoaderListener主要用來獲取spring項目的整體配置信息,並創建對應的WebApplicationContext來保存bean的信息,以及創建這些bean的對象實例。默認去WEB-INF下加載applicationContext.xml配置,如果applicationContext.xml放在其他位置,或者使用其他不同的名稱,或者使用多個xml文件,則與指定contextConfigLocation。具體spring源碼的實現過程后續文章詳細分析。
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 修改配置文件路徑 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param>
public interface Servlet { void init(ServletConfig var1) throws ServletException; ServletConfig getServletConfig(); void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; String getServletInfo(); void destroy(); }
所以在ContextLoaderListener和DispatcherServlet的創建時,都會進行WebApplicationContext的創建,這里其實就是IOC容器的創建了,即會交給spring-context,spring-beans包相關的類進行處理了,故可以從這里作為一個入口,一層一層地剝spring的源碼了。
