Spring中Bean的加載與SpringBoot的初始化流程詳解
前言
一直對它們之間的關系感到好奇,SpringBoot既然是Spring的封裝,那麼SpringBoot在初始化時應該也會有Bean的加載,那麼是在何時進行加載的呢?
第一章 Spring中Bean的一些簡單概念
1.1 SpingIOC簡介
Spring啟動時去讀取應用程序提供的Bean配置信息,並在Spring容器中生成相應的Bean定義註冊表,然後根據註冊表去實例化Bean,裝配好Bean之間的依賴關系,為上層提供準備就緒的運行環境.
Spring提供一個配置文件描述Bean與Bean之間的依賴關系,利用Java語言的反射功能實例化Bean,並建立Bean之間的依賴關系.
1.2 BeanFactory
BeanFactory是接口,提供瞭IOC容器最基本的形式,給具體的IOC容器的實現提供瞭規范。
1.2.1 BeanDefinition
主要用來描述Bean的定義,Spring在啟動時會將Xml或者註解裡Bean的定義解析成Spring內部的BeanDefinition.
beanClass保存bean的class屬性,scop保存bean是否單例,abstractFlag保存該bean是否抽象,lazyInit保存是否延遲初始化,autowireMode保存是否自動裝配,等等等
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable { private volatile Object beanClass; private String scope = SCOPE_DEFAULT; private boolean abstractFlag = false; private boolean lazyInit = false; private int autowireMode = AUTOWIRE_NO; private int dependencyCheck = DEPENDENCY_CHECK_NONE; private String[] dependsOn; private ConstructorArgumentValues constructorArgumentValues; private MutablePropertyValues propertyValues; private String factoryBeanName; private String factoryMethodName; private String initMethodName; private String destroyMethodName; }
1.2.2 BeanDefinitionRegistry
registerBeanDefinition方法主要是將BeanDefinition註冊到BeanFactory接口的實現類DefaultListableBeanFacory中的beanDefinitionMap中。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
1.2.3 BeanFactory結構圖
ListableBeanFactory該接口定義瞭訪問容器中Bean的若幹方法,如查看Bean的個數,獲取某一類型Bean的配置名,查看容器中是否包括某一Bean等方法.
HierarchicalBeanFactory是父子級聯的IOC容器接口,子容器可以通過接口方法訪問父容器,通過HierarchicalBeanFactory接口SpringIOC可以建立父子層級關聯的IOC層級體系,子容器可以訪問父容器的Bean,父容器不能訪問子容器的Bean,比如展現層的Bean位於子容器中而業務層和持久層的Bean位於父容器的Bean.
ConfigurableBeanFactory
:增強瞭IOC接口的可定制性,定義瞭設置類裝載器,屬性遍歷器,以及屬性初始化後置處理器等方法.AutowireCapableBeanFactory
:定義瞭將容器中的Bean按某種規則,按名字匹配,按類型匹配等.SingletonBeanRegistry
:允許在運行期間向容器註冊SingletonBean實例的方法.
通過這些接口也證明瞭BeanFactory的體系也確實提供瞭IOC的基礎及依賴註入和Bean的裝載等功能.
1.3 ApplicationContext
由於BeanFactory的功能還不夠強大,於是Spring在BeanFactory的基礎上還設計瞭一個更為高級的接口即ApplicationContext,它是BeanFactory的子接口之一.在我們使用SpringIOC容器時,大部分都是context的實現類。
我理解著就是BeanFactory隻提供IOC,ApplicationContext還提供很多別的功能。
第二章 SpringBoot的初始化流程
@SpringBootApplication public class RepApplication { public static void main(String[] args) { //要理解的SpringApplication SpringApplication.run(RepApplication.class, args); } }
SpringApplication的run分為兩個階段,即new SpringApplication()時的執行構造函數的準備階段,和run時的運行階段。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
2.1 準備階段
在準備階段會
配置SpringBean的來源
推斷web應用類型
加載應用上下文初始器
加載應用事件監聽器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //推斷web應用類型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); //加載應用上下文初始化器 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //加載應用事件監聽器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
2.2 運行階段
- 加載:SpringApplication獲得監聽器
- 運行:SpringApplication運行監聽器
- 監聽:SpringBoot事件、Spring事件
- 創建:應用上下文、Enviroment、其它(不重要),應用上下文創建後會被應用上下文初始化器初始化,Enviroment是抽象的環境對象。
- 失敗:故障分析報告。
- 回調:CommandLineRunner、ApplicationRunner
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); //獲得監聽器 SpringApplicationRunListeners listeners = getRunListeners(args); //運行監聽器 listeners.starting(); try { //應用上下文 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //環境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); //依據不同的配置加載不同的ApplicationContext context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
2.2.1 監聽器分析
這個是看瞭源碼後的個人理解,不保證一定正確,隻提供一定的參考。
在準備階段加載實現瞭ApplicationListener的監聽器。
然後在運行階段調用瞭starting()方法。
//獲得監聽器 SpringApplicationRunListeners listeners = getRunListeners(args); //運行監聽器 listeners.starting();
下面是listeners的源碼,可以看到它的每一個方法,都對應SpringBoot的一個階段,這表明每到對應的階段,都要廣播對應的事件。
class SpringApplicationRunListeners { private final Log log; private final List<SpringApplicationRunListener> listeners; SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); } public void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } } public void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } } public void contextPrepared(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.contextPrepared(context); } } public void contextLoaded(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.contextLoaded(context); } } public void started(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.started(context); } } //省略... }
那麼問題來瞭,廣播事件後,事件是怎麼被監聽到的呢?我們打開listener.environmentPrepared(environment)的源碼,發現其調用瞭initialMulticaster進行瞭事件廣播
@Override public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( this.application, this.args, environment)); }
廣播的代碼如下,看瞭一下感覺大意就是找到根事件匹配的監聽器,然後調用線程池去執行對應的觸發函數。
@Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } }
2.2.2 refreshContext
再往下最核心的是refreshContext方法,一直點進去可以看到如下:
prepareRefresh
:完成配置之類的解析,設置Spring的狀態,初始化屬性源信息,驗證環境信息中必須存在的屬性.ConfigurableListableBeanFactory
:是用來獲取beanFactory的實例的(第一張也寫過BeanFactory負責bean的加載與獲取)。PrepareBeanFactory
:對beanFactory進行相關的設置,為後續的使用做準備,包括設置classLoader用來加載Bean,設置表達式解析器等等.postProcessBeanFactory
:是用於在BeanFactory設置之後進行後續的BeanFactory的操作.invokeBeanFactoryPostProcessors
:點進去發現調用瞭如下方法,點進去之後發現邏輯相當復雜,主要調用工廠後處理器,調用Bean標簽,掃描Bean文件,並解析成一個個的Bean,這時候這些Bean是被加載進瞭Spirng容器當中,這裡涉及瞭各種類,我們在這裡主要說一下ConfigurationClassParser,主要是解析Bean的類.該方法會對帶有@configuration,@import,@bean,以及@SpringBootApplication等標簽的Bean進行解析,registerBeanPostProcessors
:會從Spring容器中找出實現BeanPostProcessors接口的Bean,並設置到BeanFactory的屬性之中,之後Bean實例化時會調用BeanProcessor,也就是Bean的後置處理器.會和AOP比較相關.initMessageSource
:初始化消息源(這個自己推斷的)initApplicationEventMuticaster
:初始化事件廣播器onRefresh
:是一個模板方法,不同的Spring容器會重寫它做不同的事情.比如web程序的容器,會調用create..方法去創建內置的servlet容器.registerListeners
:註冊事件監聽器finishBeanFactoryInitialization
:會實例化BeanFactory中已被註冊但未被實例化的所有實例,懶加載是不需要被實例化的.前面的invokeBeanFactoryPostProcessors方法中根據各種註解解析出來的Bean在這個時候都會被初始化,同時初始化過程中的各種PostProcessor就會開始起作用瞭.finishRefresh
:會做初始化生命周期處理器相關的事情.
@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容器關閉時的行為.
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
繼續返回上層代碼,可以看到afterRefresh方法它的方法體是空的, 也就說明Spring框架考慮瞭擴展性,留瞭很多的口子,讓大傢在框架層面繼承很多的模塊並去做自定義的實現
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { }
2.3 總結
總的來說,SpringBoot加載的Bean的時機為,點進一開始的run方法,層層遞進後,由
refreshContext(context);
進行瞭bean的加載,更詳細的話,那就層層遞進點進去,是在如下方法進行瞭bean的加載。
invokeBeanFactoryPostProcessors(beanFactory);
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Java SpringBoot核心源碼詳解
- SpringBoot詳解執行過程
- SpringBoot工程啟動順序與自定義監聽超詳細講解
- springboot加載命令行參數ApplicationArguments的實現
- Springboot自動掃描包路徑來龍去脈示例詳解