spring源碼分析之分析入口

目錄

1.引言

2.spring5架構

2.1    核心容器(Core Container)

2.2    AOP和設備支持

2.3    數據訪問及集成

2.4    Web

2.5    Messaging

2.6    Test

3.分析過程

3.1    ServletContext

3.2 root WebApplicationContext(可稱爲spring容器或者IOC容器)

3.3    子上下文(可稱爲mvc容器)

4.總結


1.引言

        只要做過java開發的IT人,基本都聽過或者用過spring框架,爲了實現快速開發,方法有很多,比如從網上下載一個ssm框架或者自己搭建一個ssm框架,再者使用當下非常流行的springboot框架。對於現在的java開發人員,spring對開發過程提供了很多方便。
        但是,你真的瞭解spring嗎?在使用spring相關方法時,可能你會根據自己的需求,到搜索引擎直接搜索如何使用該方法,會出現很多你想要的答案,而你只需要選擇最合適的,以此來完成功能的開發。但是,這些答案你有深入剖析其中的原理嗎?
        Ok,爲了解決這些問題,可能一些道聽途說的“二手”資料可以解決一些疑惑,但是都不如分析源碼得到的一手資料來的更爲直接。那麼,問題又來了,spring源碼的模塊分的比較細,模塊較多,導致無從下手啊!所以,本次分享將從實際項目代碼的角度,來引導大家分析spring源碼,這樣理解起來更爲容易些。Spring5源碼的目錄結構如下圖,

spring5源碼目錄結構

2.spring5架構

        Spring5模塊比較多,有大約20多個,都是相互獨立的,在使用上,可以聯合多個模塊使用。Spring5的核心模塊架構圖如下,

spring5架構圖

        可以劃分爲六大塊,即核心容器(Core Container)、AOP(Aspect Oriented Programming)和設備支持(Instrmentation)、數據訪問及集成(Data Access/Integeration)、Web、報文發送(Messaging)、Test。

2.1    核心容器(Core Container)

包括Beans、Core、Context 和 Expression模塊,具體介紹如下。
Beans模塊:提供了BeanFactory,springBeanFactory來生產和管理bean,是工廠模式的經典實現。
Core模塊:提供了spring框架的基本組成部分,包括IOC和DI功能。
Context模塊:建立在Beans和Code模塊的基礎上,它是訪問定義和配置任何對象的媒介。ApplicationContext接口是上下文模塊的焦點。
Expression模塊:是統一表達式語言(EL)的擴展模塊,可以查詢、管理運行中的對象,同時也方便的可以調用對象方法、操作數組、集合等。

2.2    AOP和設備支持

包括AOP、Aspects和Instrumentation模塊,具體介紹如下。
AOP模塊:提供了面向切面編程實現,允許定義方法攔截器和切入點,將代碼按照功能進行分離,以降低耦合性。
Aspects模塊:模塊集成自 AspectJ 框架,主要是爲 Spring AOP 提供多種 AOP 實現方法。
Instrumentation模塊:模塊是基於 JAVA SE 中的”java.lang.instrument”進行設計的,應該算是
AOP 的一個支援模塊,主要作用是在 JVM 啓用時,生成一個代理類,程序員通過代理類在運行時修改類
的字節,從而改變一個類的功能,實現 AOP 的功能。在分類裏,我把他分在了 AOP 模塊下,在 Spring 官
方文檔裏對這個地方也有點含糊不清,這裏是純個人觀點。

2.3    數據訪問及集成

包括 JDBC、ORM、OXM、JMS 和 Transactions 模塊,具體介紹如下。
JDBC 模塊:提供了一個 JDBC 的抽象層,大幅度減少了在開發過程中對數據庫操作的編碼。
ORM 模塊:對流行的對象關係映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成層。
OXM 模塊:提供了一個支持對象/XML 映射的抽象層實現,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。
JMS 模塊:指 Java 消息服務,包含的功能爲生產和消費的信息,自 Spring Framework 4.1以後,他還提供了對 spring-messaging 模塊的支撐。
Transactions 事務模塊:支持編程和聲明式事務管理實現特殊接口類,併爲所有的 POJO。

2.4    Web

包括 WebSocket、WebMVC、Web、WebFlux模塊,具體介紹如下。
WebSocket模塊:主要是與 Web 前端的全雙工通訊的協議。
WebMVC模塊:衆所周知是一個的Web-Servlet模塊 ,實現了Spring MVC(model-view-Controller)的Web應用。
Web模塊:爲 Spring 提供了最基礎 Web 支持,主要建立於核心容器之上,通過 Servlet 或者 Listeners 來初始化 IOC 容器,也包含一些與 Web 相關的支持。
WebFlux模塊:是一個新的非堵塞函數式 Reactive Web 框架,可以用來建立異步的,非阻塞,事件驅動的服務,並且擴展性非常好。

2.5    Messaging

Messaging模塊:從 Spring4 開始新加入的一個模塊,主要職責是爲 Spring 框架集成一些基礎的報文傳送應用。

2.6    Test

Test模塊:主要爲測試提供支持的。

3.分析過程

        spring的三大核心思想之一就是控制反轉(IOC),相比IOC,DI和AOP都依賴IOC,所以創建Spring IOC容器(可以理解爲ApplicationContext接口)是核心,在web應用中,實現ApplicationContext的接口方法用的是WebApplicationContext,結構圖如下。

WebApplicationContext類結構圖

        我們通過web應用的三個上下文來看spring源碼,web項目中用的最多的就是spring-mvc,    而spring-mvc項目中web.xml配置是必須的,配置比較多的如ContextLoaderListener,contextConfigLocation,filter,DispatcherServlet等。

3.1    ServletContext

        在servlet的規範當中,servlet容器或者叫web容器,如tomcat、jboss等中運行的每個應用都由一個ServletContext表示,在web容器中可以包含多個ServletContext,即可以有多個web應用在web容器中運行。如在tomcat的webapp目錄下,每個war包都對應一個web應用,tomcat啓動時會解壓war包,並啓動相關的應用。
        在web容器啓動的時候,會初始化web應用,即創建ServletContext對象,加載解析web.xml文件,獲取該應用的Filters,Listener,Servlet等組件的配置並創建對象實例,作爲ServletContext的屬性,保存在ServletContext當中。之後web容器接收到客戶端請求時,則會根據請求信息,匹配到處理這個請求的Servlet,同時在交給servlet處理之前,會先使用應用配置的Filters對這個請求先進行過濾,最後才交給servlet處理。

3.2 root WebApplicationContext(可稱爲spring容器或者IOC容器)

        上面提到的ContextLoaderListener,這個方法的作用就是創建root WebApplicationContext,這個root是相對的,下面會提到的。
        servlet規範當中,使用了Listener監聽器機制來進行web容器相關組件的生命週期管理以及Event事件監聽器來實現組件之間的交互。
        其中一個重要的生命週期監聽器是ServletContextListener。web容器在創建和初始化ServletContext的時候,會產生一個ServletContextEvent事件,其中ServletContextEvent包含該ServletContext的引用。然後交給在web.xml中配置的,註冊到這個ServletContext的監聽器ServletContextListener。ServletContextListener在其contextInitialized方法中定義處理邏輯,相關方法如下,篇幅有限,省略了部分代碼:

public interface ServletContextListener extends EventListener {
    void contextInitialized(ServletContextEvent var1);

    void contextDestroyed(ServletContextEvent var1);
}

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
   ...
   /**
    * Initialize the root web application context.
    */
   @Override
   public void contextInitialized(ServletContextEvent event) {
      initWebApplicationContext(event.getServletContext());
   }
   ...
}

/**
 * Initialize Spring's web application context for the given servlet context,
 * using the application context provided at construction time, or creating a new one
 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
 * @param servletContext current servlet context
 * @return the new WebApplicationContext
 * @see #ContextLoader(WebApplicationContext)
 * @see #CONTEXT_CLASS_PARAM
 * @see #CONFIG_LOCATION_PARAM
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
      throw new IllegalStateException(
            "Cannot initialize context because there is already a root application context present - " +
      "check whether you have multiple ContextLoader* definitions in your web.xml!");
   }

   servletContext.log("Initializing Spring root WebApplicationContext");
   Log logger = LogFactory.getLog(ContextLoader.class);
   if (logger.isInfoEnabled()) {
      logger.info("Root WebApplicationContext: initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
         this.context = createWebApplicationContext(servletContext);
      }
      if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         if (!cwac.isActive()) {
            // The context has not yet been refreshed -> provide services such as
            // setting the parent context, setting the application context id, etc
            if (cwac.getParent() == null) {
               // The context instance was injected without an explicit parent ->
               // determine parent for root web application context, if any.
               ApplicationContext parent = loadParentContext(servletContext);
               cwac.setParent(parent);
            }
            configureAndRefreshWebApplicationContext(cwac, servletContext);
         }
      }
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      if (ccl == ContextLoader.class.getClassLoader()) {
         currentContext = this.context;
      }
      else if (ccl != null) {
         currentContextPerThread.put(ccl, this.context);
      }

      if (logger.isInfoEnabled()) {
         long elapsedTime = System.currentTimeMillis() - startTime;
         logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
      }

      return this.context;
   }
   catch (RuntimeException | Error ex) {
      logger.error("Context initialization failed", ex);
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
      throw ex;
   }
}

        從contextInitialized代碼可知,在創建root WebApplicationContext前會先檢查是否已存在,對於不存在的情況纔會創建。
        需注意,ServletContextListeners是在Filters和Servlets創建之前接收到通知的。所以在這個時候,web應用還不能接收請求,故可以在這裏完成底層處理請求的組件的加載,這樣等之後接收請求的Filters和Servlets創建時,則可以使用這些創建好的組件了。spring相關的bean就是這裏所說的底層處理請求的組件,如數據庫連接池,數據庫事務管理器等,所以root WebApplicationContext通常都用來加載service層、dao層等業務核心代碼。
Web.xml中配置如下。

<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>

3.3    子上下文(可稱爲mvc容器)

       第三個上下文是由DispatcherServlet創建的,是root WebApplicationContext的子上下文,DispatcherServlet也叫前端控制器,通常我們說到的spring裏的父子容器,指的就是這兩個上下文,對於作用範圍而言,在DispatcherServlet中可以引用由ContextLoaderListener所創建的ApplicationContext中的內容,而反過來不行。
       在web容器中,web.xml中的加載順序:context-param -> listener -> filter -> servlet。其中ContextLoaderListener是屬於listener階段。我們通常需要在項目的web.xml中配置一個DispatcherServlet,並配置攔截包含“/”路徑的請求,即攔截所有請求。配置如下。

<servlet>
    <servlet-name>test-web</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:conf/spring/spring-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>test-web</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

       DispatcherServlet的結構圖如下,頂層是Servlet類。

DispatcherServlet類結構圖

       這樣在web容器啓動應用時,在servlet階段會創建這個servlet,由Servlet規範中servlet的生命週期方法可知:

public interface Servlet {
  void init(ServletConfig var1) throws ServletException;

  ServletConfig getServletConfig();

  void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

  String getServletInfo();

  void destroy();
}
public final void init() throws ServletException {
   ...
   // Let subclasses do whatever initialization they like.
   initServletBean();
}
protected final void initServletBean() throws ServletException {
   ...
   try {
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
   ...
}
protected WebApplicationContext initWebApplicationContext() {
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;

   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
         if (!cwac.isActive()) {
            // The context has not yet been refreshed -> provide services such as
            // setting the parent context, setting the application context id, etc
            if (cwac.getParent() == null) {
               // The context instance was injected without an explicit parent -> set
               // the root application context (if any; may be null) as the parent
               cwac.setParent(rootContext);
            }
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
   if (wac == null) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
         onRefresh(wac);
      }
   }

   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }

   return wac;
}

       web容器在創建這個servlet的時候,會調用其init方法,故可以在DispatcherServlet的init方法中定義初始化邏輯,核心實現了創建DispatcherServlet自身的一個WebApplicationContext,注意在spring中每個servlet可以包含一個獨立的WebApplicationContext來維護自身的組件,而上面通過ContextLoaderListener創建的WebApplicationContext爲共有的,通常也是最頂層,即root WebApplicationContext,servlet的WebApplicationContext可以通過setParent方法設值到自身的一個屬性。DispatcherServlet默認是加載WEB-INF下面的“servletName”-servlet.xml,來獲取配置信息的,也可以與ContextLoaderListener一樣通過contextLoaderConfig來指定位置。
        注意上面的onRefresh方法裏執行的是mvc初始化,做些HandlerMapping檢查、HandlerAdapter檢查、支持文件上傳等功能,所以子上下文通常用來加載controller,filter等方法。

4.總結

       從上面的分析,可知spring相關配置解析和組件創建其實是在web容器中,啓動一個web應用的時候,即在其ServletContext組件創建的時候,首先解析web.xml獲取該應用配置的listeners列表和servlet列表,然後保存在自身的一個屬性中,然後通過分發生命週期事件ServletContextEvent給這些listeners,從而在listeners感知到應用在啓動了,然後自定義自身的處理邏輯,如spring的ContextLoaderListener就是解析spring的配置文件並創建相關的bean,這樣其實也是實現了一種代碼的解耦;其次是創建配置的servlet列表,調用servlet的init方法,這樣servlet可以自定義初始化邏輯,DispatcherServlet就是其中一個servlet。
       所以在ContextLoaderListener和DispatcherServlet的創建時,都會進行WebApplicationContext的創建,這裏其實就是IOC容器的創建了,即會交給spring-context,spring-beans包相關的類進行處理了,而spring的其他模塊,基本都是通過配置bean的方式加載到IOC容器裏,供項目使用,各個模塊是故可以從這裏作爲一個入口,一層一層地剝spring的源碼了。

主要參考:
1)https://blog.csdn.net/lj1314ailj/article/details/80118372
2)http://blog.ibyte.vip/2019/12/12/Spring5%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E4%B8%80-%E4%BB%8E%E5%93%AA%E9%87%8C%E5%BC%80%E5%A7%8B%E7%9C%8BSpring%E6%BA%90%E7%A0%81/
3)http://blog.ibyte.vip/2019/12/20/Spring5%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90[%E4%BA%8C]-Spring-webmvc%20%E5%AE%B9%E5%99%A8%E5%88%9D%E5%A7%8B%E5%8C%96/
4)http://blog.ibyte.vip/2019/12/24/Spring5%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90[%E4%BA%8C]-Spring-webmvc%20MVC%E5%88%9D%E5%A7%8B%E5%8C%96/
5)https://www.jianshu.com/p/2854d8984dfc

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章