Spring原始碼分析——spring原始碼核心方法refresh()介紹

蘇凌峰發表於2021-10-09

1.前言

2.spring原始碼核心方法refresh()介紹

3.總結

1.前言

github原始碼地址(帶註釋):
https://github.com/su15967456...

在進入今天的spring核心方法介紹之前,我們先來複習一下上次我們學習的內容,Spring原始碼分析——spring容器總覽,如圖所示:

image.png

大致流程為:

1.IOC容器從xml,註解等地方獲取類的資訊。
2.將類的資訊封裝成BeanDefinition
3.beanFactoryPostProcessor前置處理
4.ioc容器獲取類資訊透過反射進行例項化
5.執行例項化
6.執行初始化邏輯(填充屬性(populateBean)->執行aware介面方法->beanPostProcessor:before->init-method->beanPostProcessor:after)

以上,我們只是將spring容器的執行流程大體介紹了一下,接下來,我們要進入原始碼中,大致因為我們現在是第一回看原始碼,希望不要被捲入一細節,而是大致走一遍就可以了,捲入細節的旋渦很容易勸退。)瞭解一下spring啟動的順序程式碼。

spring原始碼中,最重要的核心方法,就是refresh()了,只要看懂了refresh()中的核心的13個方法,差不多就看懂spring了!

我們在開始看原始碼前,可以看著執行過程圖猜測一下,第一步會執行什麼?

一般我們都會覺得是讀取配置檔案
但是其實是————要把beanFactory建立好。因為只有把元件初始化完畢之後,我們才會有工具類去讀配置檔案,去例項化等操作。

2.spring原始碼核心方法refresh()介紹

2.1 debug的前置工作
在開始看spring原始碼之前,我們先準備一下前置工作。

我們先寫兩個普通的類A和B(為了體現出例項化和迴圈依賴的效果,迴圈依賴我們後期會講),

類A:

public class A {

    private B b;

    public void init(){
        System.out.println("執行init()");
    }

    public void destroy(){
        System.out.println("執行destroy()");
    }

    public A(B b) {
        this.b = b;
    }

    public A() {
        System.out.println("A 建立了");
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

類B:

public class B {

    private A a;

    public B() {
        System.out.println("B 建立了");
    }

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

再寫一個配置檔案:

applicationContext.xml

image.png

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

    

    <bean id="a" class="slf.A" >
        <property name="b" ref="b"/>
    </bean>

    
    <bean id="b" class="slf.B">
        <property name="a" ref="a"/>
    </bean>


</beans>

最後寫一個啟動demo類,就算大功告成了!

public static void main(String[] args) throws Exception {
        //啟動容器
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        A a = (A) classPathXmlApplicationContext.getBean(A.class);
        B b = classPathXmlApplicationContext.getBean(B.class);

    }

我們可以在啟動容器那一行上面打上斷點,然後debug啟動!

讀spring原始碼,如何debug很重要,這裡我放上一張截圖,我們可以根據需要選擇哪個功能。

image.png

2.2spring原始碼核心方法refresh()介紹

好的,那我們先進入這個方法,點選上面那張圖的第二個箭頭:

image.png

可以看出我們進入了一個方法,但是不是ClassPathXmlApplicationContext的構造方法。

image.png

這裡我們可以不用理會,點選步出按鈕,再點步入按鈕。
image.png

就會進入到我們所希望看到的方法了:
image.png

但是現在還看不出什麼端倪,所以我們可以繼續步入:

image.png

好了,終於看到了我們的核心refresh()方法了。

    public ClassPathXmlApplicationContext(
            String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {
        //呼叫父類構造方法,進行相關的物件建立操作
        super(parent);
        //設定配置檔案路徑,放入一個物件中,方便後續的讀取和使用
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

我們可以看出,除了refresh()方法,還有一些其它的方法,我們大致地介紹一下:

2.2.1 super()方法介紹

super()主要用來呼叫父類構造方法,進行一些屬性的物件建立,後期使用。

往super()裡面點,點到最裡面,我們發現它呼叫了兩個方法:

    /**
     * Create a new AbstractApplicationContext with the given parent context.
     * @param parent the parent context
     */
    public AbstractApplicationContext(@Nullable ApplicationContext parent) {
        this();
        setParent(parent);
    }

首先我們看this()方法:

    /**
     * Create a new AbstractApplicationContext with no parent.
     */
    public AbstractApplicationContext() {
        //載入資源模式解析器 用來載入我們系統執行的時候需要的資源(xml)
        this.resourcePatternResolver = getResourcePatternResolver();
    }

透過getResourcePatternResolver方法的註釋我們可以看出,它建立了一個資源模式解析器,是後面用來加xml等載資源用的。上文提到過,我們容器的初始化前面的階段主要就是要將容器,元件進行例項化。

2.2.2 setParent(parent)方法介紹

接下來看我們的setParent(parent)方法,往裡面點我們可以看出,如果父類環境屬性不等於空,那麼久把父類容器的環境和子類容器合併。

    public void setParent(@Nullable ApplicationContext parent) {
        this.parent = parent;
        if (parent != null) {
            Environment parentEnvironment = parent.getEnvironment();
            if (parentEnvironment instanceof ConfigurableEnvironment) {
                getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
            }
        }
    }

我們可能搞不清楚父子容器的概念,因為目前spring只有一個容器,可以暫時先略過。

2.2.3 setConfigLocations(configLocations)方法介紹
這個方法比較簡單,就是將傳進來的 applicationContext.xml 這個字串儲存一下,方便後續讀取xml,可以直接從陣列中獲取。

2.2.4 refresh()方法介紹

好了,接下來終於要到我們的refresh()方法了,我們直接進入這個方法。

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

            // Prepare this context for refreshing.
            //容器重新整理前的準備工作
            /*
            1.設定容器的啟動時間
            2.設定活躍狀態為true
            3.設定關閉狀態為false
            4.獲取Environment物件,並載入當前系統的屬性到Environment中
            5.準備監聽器和事件的集合物件,預設為空的物件集合
             */
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            //建立容器物件,DefaultListableBeanFactory
            //載入xml配置檔案的屬性到當前工廠中,最重要的就是BeanDefinition
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            //準備bean工廠,完成bean工廠屬性的一些初始化操作
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                //空方法
                //子類覆蓋做額外的處理,此處我們一般不做任何擴充套件工作,這個方法具體可以做什麼,可以進去看註釋,springmvc/springboot裡有具體的擴充套件實現
                postProcessBeanFactory(beanFactory);

                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // Invoke factory processors registered as beans in the context.
                //執行beanFactoryPostProcessors,呼叫各種beanFactory處理器,比如替換工作 ${username}
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                //註冊BeanPostProcessors
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // Initialize message source for this context.
                //初始化訊息資源 國際化設定
                initMessageSource();

                // Initialize event multicaster for this context.
                //初始化應用的多播器
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                //註冊監聽器到多播器裡面
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                //例項化非懶載入的例項物件
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                //事件釋出
                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.
                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...
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }

可以看出,一共呼叫了13個方法,這13個方法就是我們要理解的關鍵,我已經相應地輸出了一些註解,接下來我們挨個進行解釋:

2.2.4.1 prepareRefresh()方法介紹

protected void prepareRefresh() {
        // Switch to active.
        //建立一下開啟時間
        this.startupDate = System.currentTimeMillis();
        //關閉狀態設定為false
        this.closed.set(false);
        //啟用狀態設定為true
        this.active.set(true);


        if (logger.isDebugEnabled()) {
            if (logger.isTraceEnabled()) {
                logger.trace("Refreshing " + this);
            } else {
                logger.debug("Refreshing " + getDisplayName());
            }
        }

        // Initialize any placeholder property sources in the context environment.
        //留給子類覆蓋,初始化屬性資源
        initPropertySources();

        // Validate that all properties marked as required are resolvable:
        // see ConfigurablePropertyResolver#setRequiredProperties
        //獲取環境物件並驗證,驗證所需要的屬性檔案是否已經放入環境中
        getEnvironment().validateRequiredProperties();

        // Store pre-refresh ApplicationListeners...
        //建立一些基本的監聽器
        if (this.earlyApplicationListeners == null) {
            this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
        } else {
            // Reset local application listeners to pre-refresh state.
            this.applicationListeners.clear();
            this.applicationListeners.addAll(this.earlyApplicationListeners);
        }

        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        //準備監聽器事件的集合
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }

我們可以看出prepareRefresh()方法主要還是做了一些準備工作,比如:
1.建立一下開啟的時間
2.設定關閉狀態
3.設定啟用狀態
4.列印日誌
5.獲取環境物件並驗證,驗證所需要的屬性檔案是否已經放入環境中
6.建立一些基本的監聽器,準備監聽器事件的集合(監聽器在後面的釋出->訂閱模式中有用到,這裡只是初始化一下類。)

我們可以看出,這個方法,主要還是做一些初始化上下文的工作,也就是重新整理前的準備工作!

2.2.4.2 obtainFreshBeanFactory()方法介紹

            // Tell the subclass to refresh the internal bean factory.
            //建立容器物件,DefaultListableBeanFactory
            //載入xml配置檔案的屬性到當前工廠中,最重要的就是BeanDefinition
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

這個方法就比較重要了,大概做了兩步操作:
1.建立一個容器物件(DefaultListableBeanFactory)
2.讀取xml的bean資訊封裝成beanDefinition的操作。

image.png

我們可以點進去看一下:

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //初始化BeanFactory,並進行xml檔案讀取,並將得到的BeanFactory記錄在當前實體的屬性中
        refreshBeanFactory();
        return getBeanFactory();//返回當前實體的beanFactory屬性
    }

我們先對容器進行初始化操作(因為容器到現在還沒進行過初始化呢),然後再讀取xml中的bean並封裝成BeanDefinition。

debug 步入 refreshBeanFactory();

    @Override
    protected final void refreshBeanFactory() throws BeansException {
        //如果有bean工廠了,先銷燬掉
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //建立DefaultListableBeanFactory物件
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            //每個容器都有自己的id,為了序列化指定id,可以從id反序列化到beanFactory物件
            beanFactory.setSerializationId(getId());
            //定製beanFactory,設定相關屬性,包括是否允許覆蓋同名稱的不同定義的物件以及迴圈依賴,可以透過子類重寫
            customizeBeanFactory(beanFactory);
            //初始化documentReader,並進行對xml檔案進行解析
            loadBeanDefinitions(beanFactory);
            this.beanFactory = beanFactory;
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

1.如果有了容器的話,先銷燬容器。
2.建立一個DefaultListableBeanFactory物件
3.接著進行相關屬性設定
4.最重要的就是對xml檔案進行解析的loadBeanDefinitions方法,把xml檔案進行解析讀取bean並封裝。(解析過程比較複雜,所以這次先不點進去看了)

2.2.4.3 prepareBeanFactory()方法介紹

接下來是對bean工廠的屬性進行一些初始化操作:

            // Prepare the bean factory for use in this context.
            //準備bean工廠,完成bean工廠屬性的一些初始化操作
            prepareBeanFactory(beanFactory);

我們可以點選進去看看:

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // Tell the internal bean factory to use the context's class loader etc.
        //設定beanFactory的類載入器為當前context的類載入器
        beanFactory.setBeanClassLoader(getClassLoader());
        if (!shouldIgnoreSpel) {
            //設定beanfactory的表示式語言處理器  處理類->解析類->配置類
            beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        }
        //為beanFactory增加一個預設的PropertyEditor,這個主要是對bean的屬性設定管理的一個工具類 屬性編輯類,比如date的format
        beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

        // Configure the bean factory with context callbacks.
        //新增beanPostProcessor 此類用來完成某些aware物件的注入
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
        //設定忽略自動裝配的介面,這些介面的實現是容器是需要 beanPostProcessor 注入的
        //所以在使用@Autowired進行注入的時候需要對這些介面進行忽略
        beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
        beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
        beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
        beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationStartupAware.class);

        // BeanFactory interface not registered as resolvable type in a plain factory.
        // MessageSource registered (and found for autowiring) as a bean.
        //設定幾個自動裝配的特殊規則,當在進行IOC的時候,如果有多個實現,就按指定的物件注入
        beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
        beanFactory.registerResolvableDependency(ResourceLoader.class, this);
        beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
        beanFactory.registerResolvableDependency(ApplicationContext.class, this);

        // Register early post-processor for detecting inner beans as ApplicationListeners.
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

        // Detect a LoadTimeWeaver and prepare for weaving, if found.
        //增加aspectJ的支援,在java中織入有三種方式,分別為編譯器織入,類載入器織入,執行時織入
        //編譯器織入指透過特殊的編譯器,將切面織入到java中
        //類載入器織入指透過特殊的類載入器,在類位元組碼載入入JVM的時候,織入切面,類似cglib
        //aspectj採用了兩種織入方式,特殊編譯器和類載入器,類載入器就是下面的load_time_wave
        if (!NativeDetector.inNativeImage() && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            // Set a temporary ClassLoader for type matching.
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }

        // Register default environment beans.
        // 註冊預設的系統環境bean到一級快取中
        if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }
        if (!beanFactory.containsLocalBean(APPLICATION_STARTUP_BEAN_NAME)) {
            beanFactory.registerSingleton(APPLICATION_STARTUP_BEAN_NAME, getApplicationStartup());
        }
    }

這個看不懂沒關係,就是對beanFactory的一些屬性值進行了一些操作。我們後續會進行講解。

2.2.4.4 postProcessBeanFactory()方法介紹

                //空方法
                //子類覆蓋做額外的處理,此處我們一般不做任何擴充套件工作,這個方法具體可以做什麼,可以進去看註釋,springmvc/springboot裡有具體的擴充套件實現
                postProcessBeanFactory(beanFactory);

2.2.4.5 invokeBeanFactoryPostProcessors()方法介紹

                // Invoke factory processors registered as beans in the context.
                //執行beanFactoryPostProcessors,呼叫各種beanFactory處理器,比如替換工作 ${username}
                invokeBeanFactoryPostProcessors(beanFactory);

這個方法主要用來執行BeanFactoryPostProcessor對BeanDefinition進行處理。
我們以後呼叫的時候再來講解。

2.2.4.6 registerBeanPostProcessors()方法介紹

                //註冊BeanPostProcessors
                registerBeanPostProcessors(beanFactory);

這個步驟初始化了一下所有的beanPostProcessor,方便後續初始化的時候進行呼叫。

2.2.4.7 beanPostProcess.end()方法介紹

空方法,模板方法,在容器重新整理的時候可以自定義邏輯,不同的Spring容器做不同的事情。

2.2.4.8 initMessageSource()方法介紹

                // Initialize message source for this context.
                //初始化訊息資源 國際化設定
                initMessageSource();

為上下文初始化 Message 源,即對不同語言的訊息體進行國際化處理。

2.2.4.9 initApplicationEventMulticaster()方法介紹

                // Initialize event multicaster for this context.
                //初始化應用的多播器
                initApplicationEventMulticaster();

初始化事件廣播器,並放入 applicationEventMulticaster bean 中,用於後面的釋出,訂閱模式事件廣播。

2.2.4.10 onRefresh()方法介紹

                // Initialize other special beans in specific context subclasses.
                //模板方法,在容器重新整理的時候可以自定義邏輯,不同的Spring容器做不同的事情。
                onRefresh();

2.2.4.11 registerListeners()方法介紹

                // Check for listener beans and register them.
                //註冊監聽器到多播器裡面
                registerListeners();

2.2.4.12 finishBeanFactoryInitialization()方法介紹

                // Instantiate all remaining (non-lazy-init) singletons.
                //例項化非懶載入的例項物件
                finishBeanFactoryInitialization(beanFactory);

這個方法就是refresh()的核心方法,建立bean,解決迴圈依賴,AOP都在這裡面。也是以後要重點講解的方法。

2.2.4.13 finishRefresh()方法介紹

                // Last step: publish corresponding event.
                //事件釋出
                finishRefresh();

前面的事件完成了,啟動釋出訂閱模式,通知另外一些類完成一些事情。

3.總結

今天我們介紹了refresh()的核心十三個方法,非常重要,我們可以debug跟一遍,然後打上註釋。
執行的流程如下:
1.容器重新整理前的準備工作
2.建立bean工廠
3.載入xml配置檔案到bean工廠中
4.對bean工廠進行賦值
5.執行beanFactoryPostProcessors
6.註冊BeanPostProcessors
7.初始化訊息資源 國際化設定
8.初始化應用的多播器
9.註冊監聽器到多播器裡面
10.例項化非懶載入的例項物件
11.事件釋出

相關文章