Spring Boot -- 啟動流程分析之ApplicationContext 中

上一節我們已經分析到AbsractApplicationContext類refresh方法中的postProcessBeanFactory方法,在分析registerBeanPostProcessors之前我們先介紹一下Spring 的鉤子介面,後面我們會在Spring源程式碼中介紹到這些鉤子介面的調用時機。

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // 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();
            }
        }
    }

一、鉤子介面介紹

Spring 提供了非常多的擴展介面,官方將這些介面稱之為鉤子,這些鉤子會在特定的時間被回調,以此來增強 Spring 功能,眾多優秀的框架也是通過擴展這些介面,來實現自身特定的功能,如 SpringBoot、mybatis 等。

二、Aware介面

Aware從字面的意思理解就是"知道"、「感知」的意思,是用來獲取Spring內部對象的介面。Aware自身是一個頂級介面,它有一系列子介面,在一個 Bean 中實現這些子介面並重寫裡面的 set 方法後,Spring 容器啟動時,就會回調該 set 方法,而相應的對象會通過方法參數傳遞進去。我們以其中的 ApplicationContextAware 介面為例。 

2.1、ApplicationContextAware

大部分 Aware 系列介面都有一個規律,它們以對象名稱為前綴,獲取的就是該對象,所以 ApplicationContextAware 獲取的對象是 ApplicationContext 。

public interface ApplicationContextAware extends Aware {

    /**
     * Set the ApplicationContext that this object runs in.
     * Normally this call will be used to initialize the object.
     * <p>Invoked after population of normal bean properties but before an init callback such
     * as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
     * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader},
     * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and
     * {@link MessageSourceAware}, if applicable.
     * @param applicationContext the ApplicationContext object to be used by this object
     * @throws ApplicationContextException in case of context initialization errors
     * @throws BeansException if thrown by application context methods
     * @see org.springframework.beans.factory.BeanInitializationException
     */
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

ApplicationContextAware 源碼非常簡單,其繼承了 Aware 介面,並定義一個 set 方法,參數就是 ApplicationContext 對象,當然,其它系列的 Aware 介面也是類似的定義。其具體使用方式如下: 

package com.goldwind.spring;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Data
@Slf4j
@Component
public class AwareTest implements ApplicationContextAware {
    /*
     * 保存應用上下文
     */
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        //輸出所有BeanDefinition name
        for(String name:applicationContext.getBeanDefinitionNames()) {
            log.info(name);
        }
    }
}

在 Spring 啟動過程中,會回調 setApplicationContext 方法,並傳入 ApplicationContext 對象,之後就可對該對象進行操作。我們獲取到ApplicationContext對象,並將所有BeanDefinition名稱輸出:

其它系列的 Aware 介面也是如此使用。具體的調用時機會在後面詳細介紹。

以下是幾種常用的 Aware 介面:

  • BeanFactoryAware:獲取 BeanFactory 對象,它是基礎的容器介面。
  • BeanNameAware:獲取 Bean 的名稱。
  • EnvironmentAware:獲取 Environment 對象,它表示整個的運行時環境,可以設置和獲取配置屬性。
  • ApplicationEventPublisherAware:獲取 ApplicationEventPublisher 對象,它是用來發布事件的。
  • ResourceLoaderAware:獲取 ResourceLoader 對象,它是獲取資源的工具。 

三、InitializingBean, DisposableBean

3.1、InitializingBean

InitializingBean 是一個可以在 Bean 的生命周期執行自定義操作的介面,凡是實現該介面的 Bean,在初始化階段都可以執行自定義的操作。

public interface InitializingBean {

    /**
     * Invoked by the containing {@code BeanFactory} after it has set all bean properties
     * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
     * <p>This method allows the bean instance to perform validation of its overall
     * configuration and final initialization when all bean properties have been set.
     * @throws Exception in the event of misconfiguration (such as failure to set an
     * essential property) or if initialization fails for any other reason
     */
    void afterPropertiesSet() throws Exception;

}

從 InitializingBean 源碼中可以看出它有一個 afterPropertiesSet 方法,當一個 Bean 實現該介面時,在 Bean 的初始化階段,會回調 afterPropertiesSet 方法,其初始化階段具體指 Bean 設置完屬性之後。

3.2、DisposableBean

同理,DisposableBean在Bean銷毀時執行自定義的操作,必須資源的釋放。

public interface DisposableBean {

    /**
     * Invoked by the containing {@code BeanFactory} on destruction of a bean.
     * @throws Exception in case of shutdown errors. Exceptions will get logged
     * but not rethrown to allow other beans to release their resources as well.
     */
    void destroy() throws Exception;

}

比如:

package com.goldwind.spring;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@Slf4j
public class BeanTest implements InitializingBean, DisposableBean {
    /*
     * 構造函數
     */
    public BeanTest(){
        log.info("New object...");
    }

    @Override
    public void destroy()  {
        log.info("Destroying ...");
    }

    @Override
    public void afterPropertiesSet() {
        log.info("Initializing ....");
    }
}

四、BeanPostProcessor、BeanFactoryPostProcessor

4.1、BeanPostProcessor

BeanPostProcessor 和 InitializingBean 有點類似,也是可以在 Bean 的生命周期執行自定義操作,一般稱之為 Bean 的後置處理器,不同的是, BeanPostProcessor 可以在 Bean 初始化前、後執行自定義操作,且針對的目標也不同,InitializingBean 針對的是實現 InitializingBean 介面的 Bean,而 BeanPostProcessor 針對的是所有的 Bean。並且postProcessBeforeInitialization在對象創建之後,afterPropertiesSet之前執行,而postProcessAfterInitialization在afterPropertiesSet之後執行:

public interface BeanPostProcessor {

    // Bean 初始化前調用
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    // Bean 初始化後調用
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

所有的 Bean 在初始化前、後都會回調介面中的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法,入參是當前正在初始化的 Bean 對象和 BeanName。值得注意的是 Spring 內置了非常多的 BeanPostProcessor ,以此來完善自身功能,這部分會在後面文章深入討論。 

我們擴充我們的測試類AwareTest :

package com.goldwind.spring;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Data
@Slf4j
@Component
public class AwareTest implements ApplicationContextAware, BeanPostProcessor {
    /*
     * 保存應用上下文
     */
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        //輸出所有BeanDefinition name
        for(String name:applicationContext.getBeanDefinitionNames()) {
            log.info(name);
        }
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)  {
        if(beanName.equals("beanTest")) {
        log.info("postProcessBeforeInitialization:" + beanName);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)  {
        if(beanName.equals("beanTest")) {
            log.info("postProcessAfterInitialization:" + beanName);
        }
        return bean;
    }

}

BeanTest :

package com.goldwind.spring;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@Slf4j
public class BeanTest implements InitializingBean, DisposableBean, BeanNameAware {
    /*
     * 保存當前bean name
     */
    private String beanName;

    /*
     * 構造函數
     */
    public BeanTest(){
        log.info("New object...");
    }

    @Override
    public void destroy()  {
        log.info("Destroying ...");
    }

    @Override
    public void afterPropertiesSet() {
        log.info("Initializing ....");
    }


    @Override
    public void setBeanName(String name) {
        this.beanName = name;
        log.info("Current bean name:" + name);
    }
}

可以看到beanTest對象先是被實例化出來,然後執行BeanPostProcessor的postProcessBeforeInitialization,再執行InitializingBean的afterPropertiesSet,最後執行BeanPostProcessor的postProcessAfterInitialization方法。而ApplicationContextAware的setApplicationContext方法執行時所有BeanDefinition都已載入,但還未實例化Bean

BeanPostProcessor 使用場景其實非常多,因為它可以獲取正在初始化的 Bean 對象,然後可以對Bean 對象做一些訂製化的操作,如:判斷該 Bean 是否為某個特定對象、獲取 Bean 的註解元數據等。事實上,Spring 內部也正是這樣使用的,之前我們介紹的Spring Boot -- Spring AOP原理及簡單實現手寫AOP時也是利用了BeanPostProcessor的特性,我們對@Pointcut註解指定的Bean都進行了代理處理。

4.2、BeanFactoryPostProcessor

BeanFactoryPostProcessor 是 Bean 工廠的後置處理器,一般用來修改上下文中的 BeanDefinition,修改 Bean 的屬性值。

public interface BeanFactoryPostProcessor {
    // 入參是一個 Bean 工廠:ConfigurableListableBeanFactory。該方法執行時,所有 BeanDefinition 都已被載入,但還未實例化 Bean。
    // 可以對其進行覆蓋或添加屬性,甚至可以用於初始化 Bean。
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanFactoryPostProcessor 源碼非常簡單,其提供了一個 postProcessBeanFactory 方法,當所有的 BeanDefinition 被載入時,該方法會被回調。值得注意的是,Spring 內置了許多 BeanFactoryPostProcessor 的實現,以此來完善自身功能。 這裡,我們來實現一個自定義的 BeanFactoryPostProcessor:

package com.goldwind.spring;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.stereotype.Component;

@Data
@Slf4j
@Component
public class AwareTest implements  BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        log.info("------------------------------------------postProcessBeanFactory-------------------------");
        String beanNames[] = beanFactory.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            log.info(beanDefinition.toString());
        }
    }
}

 主要是通過 Bean 工廠獲取所有的 BeanDefinition 。

可以看到,BeanDefinition 正確輸出,裡面是一些 Bean 的相關定義,如:是否懶載入、Bean 的 Class 以及 Bean 的屬性等。

五、ImportSelector

參考文章:

[1]Spring(七)核心容器 - 鉤子介面(轉載)