Spring源碼閱讀系列之初始化容器整體流程介紹

上篇文章提到,因爲 Spring 源碼非常龐大,所以閱讀 Spring 源碼的最直接有效的辦法就是 Debug,後續源碼也是基於這種方式去讀。接下來看看一個最簡單的例子

  1. 創建一個UserService接口和其實現類

public interface UserService {
    void save();
}

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("執行user信息保存操作");
    }
}

  1. 創建一個applicationContext.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="UserServiceImpl"></bean>
</beans>

  1. 編寫一個 JUnite 測試類

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JuniteTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 1.通過byType的方式獲取bean實例
        UserService service = context.getBean(UserService.class);
        service.save();
        System.out.println("-----------");
        // 2.通過byName的方式獲取bean實例
        UserService service1 = (UserService) context.getBean("userService");
        service1.save();
    }
}

  1. 從 Junite 測試類 創建Context開始,debug 進去可以看到下面這段代碼

public ClassPathXmlApplicationContext(
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
      throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
      // 這裏就是真正初始化Spring容器的地方,也就是我在上一篇文章最後提供的那張圖裏的流程
      // 有同學會問,那Tomcat啓動的時候是怎麼初始化Spring容器的呢?
      // 其實也比較簡單,一般在Tomcat裏面的web.xml文件配置了一個監聽器,大概長這個樣子
      //<listener>
      //  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      //</listener>
      // 該監聽器調用contextInitialized()方法,一路點進去可以看到最後也是調用了AbstractApplicationContext.refresh()方法
      refresh();
    }
}

  1. 找到了 Spring 容器初始化的入口,接下來就來詳細看看我們單測裏面用到的userService實例是怎麼被創建出來的

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      // Step1:其主要作用就是爲當前的context上下文初始化一些基礎參數
      // 比如說:初始化時間,活動狀態,日誌信息等等,暫時不重要,不需要關注其太多的實現細節
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      // Step2: 從註釋上看是通知子類去刷新內部bean工廠。解釋的好像也特別抽象不知道它到底想幹啥
      // **重要** 其實在這個方法裏面做了幾個非常重要的事情,創建bean工廠,並將BeanDefinitions對象(bean實例的定義,這個對象也會貫穿整個Spring源碼)存到bean工廠中
      // 創建出來的ConfigurableListableBeanFactory,這個bean工廠會貫穿整個Spring源碼
      // 它功能是存放beanDefinition定義,後續創建出來的bean實例也存放這這個工廠裏面
      // 暫時先理解到這裏就可以了,後續我們會展開其裏面的具體實現
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      // Step3:配置bean工廠的上下文,
      // 比如在這個裏面配置了ClassLoader,
      // 配置了BeanPostProcessor(bean後置處理器,這玩意兒會在bean實例初始化前後做一些增強工作,後面會詳細介紹)
      // 註冊了三個默認bean實例,分別是 “environment”、“systemProperties”、“systemEnvironment” 瞭解即可
      prepareBeanFactory(beanFactory);

      try {
        // Allows post-processing of the bean factory in context subclasses.
        // Step4:本方法沒有具體實現,是一個擴展點,開發人員可以根據自己的情況做具體的實現
        postProcessBeanFactory(beanFactory);

        // Invoke factory processors registered as beans in the context.
        // Step5:bean工廠後置處理器,主要是對beanFactory對象做一些後置操作
        // 和registerBeanPostProcessor名字有點像,只不過後者操作的對象bean實例
        // 後續會深入瞭解其實現原理
        invokeBeanFactoryPostProcessors(beanFactory);

        // Register bean processors that intercept bean creation.
        // Step6:註冊所有實現了BeanPostProcessor接口的對象,
        // 注意:這裏只是註冊實例,並沒有執行其方法,方法具體調用時機是在bean實例初始化前後執行的
        registerBeanPostProcessors(beanFactory);

        // Initialize message source for this context.
        // Step7:初始化message資源,做一些國際化相關操作,瞭解即可
        initMessageSource();

        // Initialize event multicaster for this context.
        // Step8:初始化事件廣播器,用於發送相關事件
        // 比如註冊了一個bean實例的時候就會發送一個事件
        initApplicationEventMulticaster();

        // Initialize other special beans in specific context subclasses.
        // Step9:在一些特定的context中初始化一些特殊的bean,沒有具體實現,留給開發者自定義一些操作
        onRefresh();

        // Check for listener beans and register them.
        // Step10:註冊監聽器,瞭解即可
        registerListeners();

        // Instantiate all remaining (non-lazy-init) singletons.
        // Step11:初始化所有非懶加載的bean實例,注意是非懶加載的bean
        //**非常重要** 我們代碼中需要用到的bean實例幾乎都是從這裏創建出來的,後面會展開其具體實現
        finishBeanFactoryInitialization(beanFactory);

        // Last step: publish corresponding event.
        // Step12:完成上線文刷新,發佈上下文初始化完畢的事件
        finishRefresh();
      }

      catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
          logger.warn("Exception encountered during context initialization - " +
              "cancelling refresh attempt: " + ex);
        }

        // Destroy already created singletons to avoid dangling resources.
        // 當創建bean發生異常時,先銷燬已經創建出來的單例bean實例,從而避免資源一直被佔用。
        // 這樣的做法有點像關閉資源鏈接,比如當程序聯繫MySQL或Redis的時候,程序發生異常時需要將鏈接斷開一樣
        destroyBeans();

        // Reset 'active' flag.
        // 重置上下文的標識
        cancelRefresh(ex);

        // Propagate exception to caller.
        throw ex;
      }

      finally {
        // Reset common introspection caches in Spring's core, since we
        // might not ever need metadata for singleton beans anymore...
        // Step13:重置公共的一些緩存數據
        resetCommonCaches();
      }
    }
}

總結

Spring 容器初始化,共經歷了 13 步;其中尤其需要重點關注的是:

  • Step2,初始化 Spring 容器,並構建了BeanDefinition定義

  • Step5BeanFactoryPostProcessor,對BeanFactory做一些後置操作

  • Step7BeanPostProcessor,對 bean 實例在初始化前後做一些增強工作Step11,對剩餘所有的非懶加載的BeanDefinition(bean 定義)執行 bean 實例化操作

下一篇文章我們來講講 Step2,obtainFreshBeanFactory()實現原理。


歡迎關注我,共同學習

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