一.應用啟動類
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
開發SpirngBoot應用時,入口類就這簡單的幾行。但是卻完成了N多服務的初始化、加載和發布。那么這幾行代碼究竟干了什么呢,SpringBoot應用到底是怎么啟動的。
本文中相關源碼來自Springboot2.3.3, spring不同版本之間的代碼可能有些許差別,但整體的過程是大同小異的
二.@SpringBootApplication注解
2.1.SpringBootApplication注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@SpringBootApplication=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan,是這三個注解的復合注解
2.2.@SpringBootConfiguration
/**
* Indicates that a class Spring Boot application
* {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
* standard {@code @Configuration} annotation so that configuration can be found
* automatically (for example in tests).
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}
SpringBootConfiguration注解和Spring的@Configuration注解作用一樣。標注當前類是配置類,并會將當前類內聲明的一個或多個以@Bean注解標記的方法的實例納入到spring容器中.
2.3.@ComponentScan
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
/**
* Configures component scanning directives for use with @{@link Configuration} classes.
* Provides support parallel with Spring XML's {@code <context:component-scan>} element.
*
* <p>Either {@link #basePackageClasses} or {@link #basePackages} (or its alias
* {@link #value}) may be specified to define specific packages to scan. If specific
* packages are not defined, scanning will occur from the package of the
* class that declares this annotation.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan{}
@ComponentScan掃描指定的包路徑,若未指定包路徑,則以聲明這個注解的類作為基本包路徑。比如@SpringBootApplication就沒有指定包路徑,則DemoApplication的包路徑將作為掃描的基本包路徑,因此強烈建議將主類放在頂層目錄下。
excludeFilters屬性指定哪些類型不符合組件掃描的條件,會在掃描的時候過濾掉。
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
比如上面這段代碼。@Filter聲明了過濾器類型類為自定義類型(需要實現TypeFilter接口),過濾器為AutoConfigurationExcludeFilter。當match方法為true,返回掃描類對象,否則過濾掉。但是要注意@ComponentScan的key為excludeFilters,因此ComponentScan在掃描時滿足過濾器條件(match返回true)的這些類型將在包掃描的時候過濾掉,是不會將該類加載到容器的。
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
//如果是Springboot自動配置類,則不將其加載到Bean容器
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
//判斷是否是配置類
private boolean isConfiguration(MetadataReader metadataReader) {
return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}
//判斷是否為EnableAutoConfiguration類
private boolean isAutoConfiguration(MetadataReader metadataReader) {
return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}
2.3.@EnableAutoConfiguration
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
這個注解是SpringBoot能進行自動配置的關鍵
selectImports
方法:根據導入Configuration類的AnnotationMetadata選擇并返回應導入的類的名稱
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
//返回自動配置類名數組
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//根據導入Configuration類的AnnotationMetadata返回AutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//返回@EnableAutoConfiguration 注解上的排除屬性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//通過SPI加載候選的自動配置類名
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//移除重復的類名
configurations = removeDuplicates(configurations);
//獲取注解上和 spring.autoconfigure.exclude 配置的排除類名
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//檢查加載到的類名的合法性
checkExcludedClasses(configurations, exclusions);
//排除需要移除的類
configurations.removeAll(exclusions);
//通過元數據再次過濾
configurations = getConfigurationClassFilter().filter(configurations);
//監聽器發布自動配置導入事件并進行相應的處理
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
@Import注解用于導入配置類,導入類AutoConfigurationImportSelector。
在 AbstractApplicationContext--->refresh()--->invokeBeanFactoryPostProcessors()中,會調用AutoConfigurationImportSelector類的selectImports方法,最終通過調SpringFactoriesLoader.loadFactoryNames方法,
掃描META-INF/spring.factories文件自動配置類(key為EnableAutoConfiguration),通過對其全類名的反射獲取到自動導入類的類元信息,并注冊到Bean工廠
三.從SpringApplication.run開始解析
3.1 Springboot的啟動流程主要分為三個部分:
- SpringApplication的創建和初始化以及啟動之前的一些配置(啟動前)
- SpringApplication的具體啟動過程(啟動過程)
- SpringBoot的核心即自動配置模塊
3.2.SpringApplication的創建和初始化
3.2.1.構造器
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1.根據應用是否存在某些類推斷應用類型,分為響應式web應用,servlet類型web應用和非web應用,在后面用于確定實例化applicationContext的類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2.設置初始化器,讀取spring.factories文件key ApplicationContextInitializer對應的value并實例化.ApplicationContextInitializer接口用于在Spring上下文被刷新之前進行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//3.設置監聽器,讀取spring.factories文件key ApplicationListener對應的value并實例化
//interface ApplicationListener<E extends ApplicationEvent> extends EventListener
//ApplicationListener繼承EventListener,實現了觀察者模式。對于Spring框架的觀察者模式實現,它限定感興趣的事件類型需要是ApplicationEvent類型事件
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4.配置應用入口類class對象
this.mainApplicationClass = deduceMainApplicationClass();
}
如上源碼,在構造器里主要干了2件事,一是設置初始化器,二是設置監聽器
3.2.2.設置初始化器ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//從類路徑的META-INF處讀取相應配置文件spring.factories,然后進行遍歷,讀取配置文件中Key(type)對應的value
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//通過反射將names的對象實例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
根據入參type類型ApplicationContextInitializer.class從類路徑的META-INF處讀取相應配置文件spring.factories并實例化對應Initializer。
SpringApplication啟動中獲取指定自動配置類型的實例時反復用到了上面這2個函數。
- ApplicationContextInitializer是Spring框架原有的東西,這個類的主要作用就是在ConfigurableApplicationContext類型(或者子類型)的ApplicationContext做refresh之前,允許我們對ConfiurableApplicationContext的實例做進一步的設置和處理。關于Spring中具體的ApplicationContextInitializer介紹請移步這里:Spring中的ApplicationContextInitializer
SpringBoot默認META-INF/spring.factories中的ApplicationContextInitializer配置如下:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
3.2.3.設置監聽器ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
和設置初始化器是相同的過程,通過getSpringFactoriesInstances函數實例化監聽器。
- ApplicationListener使用了觀察者設計模式,主要作用是在springboot啟動過程的不同階段,通過監聽到發布的不同的事件從而去執行一些相應的操作。關于Spring中具體的ApplicationListener介紹請移步這里:Spring中的監聽器詳解與觀察者模式
SpringBoot默認META-INF/spring.factories中的ApplicationListener配置如下:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
3.3.SpringApplication具體的啟動過程分析
3.3.1.啟動過程的核心 run方法
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//計時器,記錄程序的運行時間
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//設置java.awt.headless系統屬性為true,Headless模式是系統的一種配置模式。
// 在該模式下,系統缺少了顯示設備、鍵盤或鼠標。但是服務器生成的數據需要提供給顯示設備等使用。
// 因此使用headless模式,一般是在程序開始激活headless模式,告訴程序,現在你要工作在Headless mode下,依靠系統的計算能力模擬出這些特性來
configureHeadlessProperty();
//獲取監聽器集合對象
SpringApplicationRunListeners listeners = getRunListeners(args);
//1.發出開始執行的事件。
listeners.starting();
try {
//根據main函數傳入的參數,創建DefaultApplicationArguments對象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2.根據掃描到的監聽器對象和函數傳入參數,進行環境準備。
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
//讀取spring.beaninfo.ignore,并加入到Spring內部的Bean信息緩存中
configureIgnoreBeanInfo(environment);
//通過Banner.Model和相關的配置打印Banner信息
Banner printedBanner = printBanner(environment);
//創建ApplicationContext,類型為ConfigurableApplicationContext
context = createApplicationContext();
//和上面套路一樣,讀取spring.factories文件key SpringBootExceptionReporter對應的Class,用于支持SpringbootApplication啟動過程中異常的回調
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
//準備和加載運行環境
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
//和上面的一樣,context準備完成之后,將觸發SpringApplicationRunListener的contextPrepared執行
refreshContext(context);
//其實啥也沒干。但是老版本的callRunners好像是在這里執行的。
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//發布ApplicationStartedEvent事件,發出結束執行的事件
listeners.started(context);
//在某些情況下,我們希望在容器bean加載完成后執行一些操作,會實現ApplicationRunner或者CommandLineRunner接口
//后置操作,就是在容器完成刷新后,依次調用注冊的Runners,多個Runner時可以通過@Order注解設置各runner的執行順序。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}try {
//發布ApplicationReadyEvent事件。上下文已刷新并且所有的CommandLineRunners和ApplicationRunner都已被調用,應用上下文創建完成
listeners.running(context);
}catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
SpringApplication核心的啟動運行方法如上所示,過程分析:
- listeners.starting(): 發布ApplicationStartingEvent,運行過程開始
- 創建并配置運行環境:
- 程序運行的環境,主要包含了兩種信息,一種是profiles,用來描述哪些bean definitions是可用的;一種是properties,用來描述系統的配置,其來源可能是配置文件、JVM屬性文件、操作系統環境變量等等。
- 發布ApplicationEnvironmentPreparedEvent環境準備就緒事件
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } configurePropertySources(environment, args); configureProfiles(environment, args); }
- context = createApplicationContext():創建ApplicationContext
這里通過this.webApplicationType判斷具體要創建哪種類型的ApplicationContext.protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
比如web類型為servlet類型,就會實例化org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext類型的context。
在這里BeanUtils.instantiateClass(contextClass)是通過instantiateClass(clazz.getDeclaredConstructor())進行初始化,也就是說這里是用來空參構造函數來進行實例化的。下面是其構造函數:
構造方法中初始化了兩個成員變量,類型分別為AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner用以加載使用注解的bean定義:/** * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs * to be populated through {@link #register} calls and then manually * {@linkplain #refresh refreshed}. */ public AnnotationConfigServletWebServerApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); }
在實例化上下文的時候,會向ApplicationContext.beanFacroty內部注冊幾個核心的后處理器:// 調用父類AnnotationConfigApplicationContext的構造函數 public AnnotationConfigApplicationContext() { // 實例化一個注解bena定義讀取器 this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); // 維護一個ApplicationContext上下文索引 this.registry = registry; // 聲明一個條件評估器,用來評估一個@Condition注解的類是否符合注入條件 this.conditionEvaluator = new ConditionEvaluator(registry, environment, null); // 這里會事先向beanFacroty注入幾個核心后處理器 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); }
- internalConfigurationAnnotationProcessor:ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor: 用于@Configuration配置類的處理,包括postProcessBeanDefinitionRegistry,postProcessBeanFactory,processConfigBeanDefinitions三個主要方法
- internalAutowiredAnnotationProcessor:AutowiredAnnotationBeanPostProcessor:處理@Autowired,@Injected注解字段,向其注入實際依賴的bean
- internalCommonAnnotationProcessor:CommonAnnotationBeanPostProcessor: 處理@PostConstruct,@PreDestroy,@Resource等注解
- internalPersistenceAnnotationProcessor:PersistenceAnnotationBeanPostProcessor:如果引入了JPA,這個類時處理JPA注解
- internalEventListenerProcessor:EventListenerMethodProcessor:將EventListenner方法注冊為ApplicationListener
- internalEventListenerFactory:DefaultEventListenerFactory:默認的EventListenerFactory實現,支持@EventListener注解
- prepareContext(context, environment, listeners, applicationArguments,printedBanner):context前置處理階段
private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) { //關聯環境 context.setEnvironment(environment); //ApplicationContext預處理,主要配置Bean生成器以及資源加載器 postProcessApplicationContext(context); //調用初始化器,執行initialize方法,前面set的初始化器終于用上了 applyInitializers(context); //發布contextPrepared事件 listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //注冊單例Bean ApplicationArguments到ConfigurableListableBeanFactory,用于獲取啟動application所需的參數 beanFactory.registerSingleton("springApplicationArguments", applicationArguments); //加載打印Banner的Bean if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { //設置是否允許重載Bean定義 ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Load the sources,根據primarySources加載resource。primarySources:一般為主類的class對象 Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); //構造BeanDefinitionLoader并完成定義的Bean的加載 load(context, sources.toArray(new Object[0])); //發布ApplicationPreparedEvent事件,表示application已準備完成 listeners.contextLoaded(context); }
- refreshContext: 調用父類AbstractApplicationContext刷新容器的操作.這里的refresh()方法就是Spring IOC容器加載的核心過程。Spring核心源碼分析請看Spring IOC 容器源碼分析
3.3.2.啟動過程中的監聽器的使用
SpringApplicationRunListeners listeners = getRunListeners(args);
和構造器設置初始化器一個套路,根據傳入type SpringApplicationRunListener去掃描spring.factories文件,讀取type對應的value并實例化。然后利用實例化對象創建SpringApplicationRunListeners對象。
查看spring.factories中的配置如下:
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
EventPublishingRunListener的作用是在SpringApplication加載的不同階段發布不同的SpringApplicationEvent。如下是其調用各個方法相應的階段,也對應了run方法運行過程中的多個階段:
@Override
//在run方法首次啟動時立即調用。可用于非常早期的初始化
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@Override
//在準備好環境之后,ApplicationContext創建之前調用
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
@Override
//在ApplicationContext已經被創建和準備完畢之后,在加載資源前被調用
public void contextPrepared(ConfigurableApplicationContext context) {
this.initialMulticaster
.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
@Override
//ApplicationContext上下文被加載后,刷新之前
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
//上下文已刷新,應用程序已啟動,但尚未調用CommandLineRunners和ApplicationRunners
public void started(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
@Override
//在上下文已刷新并且所有的CommandLineRunners和ApplicationRunner都已被調用,run方法完成之前
public void running(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
@Override
//應用程序運行過程中發生異常時被調用
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
三、自動配置的奧秘---SpringBoot啟動過程中自動配置Bean如何注冊到BeanFactory
在 Spring-IOC容器源碼分析一文中,分析到SpringBoot基于注解的運行方式是在 refresh()--->invokeBeanFactoryPostProcessors()方法中進行了Bean定義的解析和收集,那么自動配置類導入的Bean或直接使用或間接的去構建成其他對象,必然也需要在這一階段進行Bean定義的注冊以便在之后的過程中進行實例化
承接 Spring-IOC容器源碼分析一文中 ConfigurationClassParser.java 265--->doProcessConfigurationClass方法對自動配置相關的源碼繼續分析:
@EnableAutoConfiguration注解中通過
@Import(AutoConfigurationImportSelector.class)
導入了自動配置類,在這里我們直接從處理@Import注解的
processImports(configClass, sourceClass, getImports(sourceClass), filter, true)
開始分析
ConfigurationClassParser.java 552
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
//對ImportSelector的處理
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
//如果是 DefferredImportSelector,則使用deferredImportSelectorHandler進行延遲處理
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
//根據ImportSelector方法的返回值來進行遞歸操作
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
//如果當前的類既不是ImportSelector也不是ImportBeanDefinitionRegistar就進行@Configuration的解析處理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
可以看到
DeferredImportSelectorHandler
是ConfigurationClassParser
一個專門用來處理延遲導入選擇器的內部類
關于SpringBoot中的其他的ImportSelctor
類的使用和分析 spring中的ImportSelector接口原理與使用
真正的對于延遲ImportSelector的處理則是在下面的process()
方法:
ConfigurationClassParser.java 169
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
//延遲導入選擇器的處理,SpringBoot自動配置類的加載處理的關鍵
//因為有些自動配置類是有條件的,需要根據@Condition注解判斷是否已經有指定類再進行注入
//所以在這里需要等到所有的配置類都處理完以后,最后處理這些 DeferredImportSelector類
this.deferredImportSelectorHandler.process();
}
ConfigurationClassParser.java 746
private class DeferredImportSelectorHandler {
@Nullable
private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();
/**
* Handle the specified {@link DeferredImportSelector}. If deferred import
* selectors are being collected, this registers this instance to the list. If
* they are being processed, the {@link DeferredImportSelector} is also processed
* immediately according to its {@link DeferredImportSelector.Group}.
* //處理指定的 DeferredImportSelector。如果正在收集延遲導入選擇器,則會將此實例注冊到列表中。如果正在處理它們,將會根據DeferredImportSelector.Group組立即處理
* @param configClass the source configuration class
* @param importSelector the selector to handle
*/
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
//根據私有變量deferredImportSelectors初始化值,如果直接執行該handle方法時,this.deferredImportSelectors == null條件比不成立
if (this.deferredImportSelectors == null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder);
handler.processGroupImports();
}
else {
//將加入的importSelector封裝后添加到DeferredImportSelectorHolder集合
this.deferredImportSelectors.add(holder);
}
}
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
//創建一個組處理器
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
//根據@Order注解進行排序
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
//循環注冊所有的 ImportSelector到相應的組中
deferredImports.forEach(handler::register);
//所有組分別處理相應的ImportSelector
//DeferredImportSelector會根據Group進行分組,即封裝成 DeferredImportSelectorGrouping 類,并且以組為單位對同一組中的ImportSelector進行統一處理
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
}
接著上面的process方法分析:
ConfigurationClassParser.java 795
public void register(DeferredImportSelectorHolder deferredImport) {
// key:組類型(在這里 AutoConfigurationGroup) value:組
private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
// key:配置類的注解屬性 value:配置類信息(在這里是入口類即具有@SpringBootApplication類的信息)
private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
//注冊分組
public void register(DeferredImportSelectorHolder deferredImport) {
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup(); // 這個方法有默認(default)實現,返回的是 null
/*
創建組
1. 其中 createGroup(group) 就是創建了上面的 group 對象,如果為空,則創建一個默認的組對象 DefaultDeferredImportSelectorGroup。
2. 這個方法的意思是,如果 map 中沒有這個元素則用后面的方法創建,如果有則直接取出來
*/
DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
(group != null ? group : deferredImport),
key -> new DeferredImportSelectorGrouping(createGroup(group)));
grouping.add(deferredImport);//創建一個組,并加入DeferredImportSelectorHolder
this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getConfigurationClass());//將注解屬性和ConfigurationClass映射
}
ConfigurationClassParser.java 805
public void processGroupImports() {
//遍歷其中的ImportSelectorGroup進行處理
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
//關鍵點 對于默認DefaultDeferredImportSelectorGroup組下的selector直接將其類信息封裝成Entry信息返回,對于AutoConfigurationGroup組下的在下面分析
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
//遞歸調用處理 @Import的方法
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
ConfigurationClassParser.java 874
public Iterable<Group.Entry> getImports() {
//遍歷使用指定的DeferredImportSelector處理導入Configuration類的AnnotationMetadata
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
//返回該組中需要被導入的Entries
return this.group.selectImports();
}
在這里繼續看AutoConfigurationGroup
類中對于上述 void process(AnnotationMetadata metadata, DeferredImportSelector selector)
類的實現:
AutoConfigurationImportSelector.java 428
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
//關鍵點 基于導入@Configuration類的AnnotationMetadata返回AutoConfigurationEntry,與最開始通過SPI獲取自動配置類信息的分析銜接
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
與DeferredImportSelector
處理相關的主要有這幾個類:
- DeferredImportSelectorHandler:持有一個List<DeferredImportSelectorHolder>類型的list,是對 DeferredImportSelector 類型的處理類
- DeferredImportSelectorGroupingHandler:DeferredImportSelector的實際分組處理類,持有如下的兩個屬性,其 register 和 processGroupImports 方法處理 DeferredImportSelector 并填充這兩個屬性
private class DeferredImportSelectorGroupingHandler {
private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
...
}
- DeferredImportSelectorGrouping:持有一個DeferredImportSelector.Group組對象和DeferredImportSelectorHolder的List,存放該組中要處理的 DeferredImportSelector
private static class DeferredImportSelectorGrouping {
private final DeferredImportSelector.Group group;
private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
...
}
- DeferredImportSelectorHolder:DeferredImportSelector的封裝,持有DeferredImportSelector實例及其對應的Configuration類元信息
private static class DeferredImportSelectorHolder {
private final ConfigurationClass configurationClass;
private final DeferredImportSelector importSelector;
...
}
總體上的處理過程如下:四.開發一個自己的Starter
與Starter相關的內容其實是Springboot自動配置的部分,下面將之前的使用Netty-websocket構建一個簡易的聊天室改造成一個Starter
3.1編寫一個Starter主要是這么幾步:
- 加入spring-boot-autoconfigure配置
- 編寫自動配置類
/** * 自動裝配引導類 * * @author duwenxu * @create 2021-01-21 18:29 */ @Configuration //僅當ChatServerStarter存在于類路徑上時才會實例化Bean @ConditionalOnClass(ChatServerStarter.class) public class NettyWsAutoConfiguration { @Bean //僅當該BeanFactory中不存在chatServerStarter類型的Bean時注入該Bean @ConditionalOnMissingBean public ChatServerStarter chatServerStarter(){ return new ChatServerStarter(); } }
- 在META-INF文件夾下添加spring.factories添加自動配置類
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.netty.websocket.autoconf.NettyWsAutoConfiguration
- maven構建jar包:
mvn clean install
3.2.需要注意:
- starter是具有一個工具包的性質,因次應該去掉主類以及pom中的mainClass配置,否則會出現不能夠注入bean的問題
3.3. 引用后的效果:
-
加入依賴
maven依賴信息.png -
日志打印websocket端口的綁定信息
打印端口綁定信息.png -
打印新Channel連入信息
打印新的channel連接信息.png -
可以實現websocket長連接推送功能
在線測試.png