版本
以下源碼的 SpringBoot
版本:2.3.9.RELEASE。
總體上
分為兩大步:
- 啟動類上注解:@SpringBootApplication
- 啟動類中的main方法:org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)
main方法上的注解:@SpringBootApplication
源碼
三個注解核心注解:@SpringBootConfiguration
,@EnableAutoConfiguration
和@ComponentScan
。
@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 {
@SpringBootConfiguration
根據Javadoc可知,該注解作用就是將當前的類作為一個JavaConfig,然后觸發注解@EnableAutoConfiguration
和@ComponentScan
的處理,本質上與@Configuration注解沒有區別。
@ComponentScan
掃描的 Spring
對應的組件,如 @Componet,@Repository。
我們可以通過 basePackages 等屬性來細粒度的定制 @ComponentScan
自動掃描的范圍,如果不指定,則默認Spring框架實現會從聲明 @ComponentScan
所在類的package進行掃描,所以 SpringBoot
的啟動類最好是放在根package下,我們自定義的類就放在對應的子package下,這樣就可以不指定 basePackages
。
@EnableAutoConfiguration
@EnableAutoConfiguration總結
-
@AutoConfigurationPackage
- 注冊當前啟動類的根 package
- 注冊 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的 BeanDefinition。
-
@Import(AutoConfigurationImportSelector.class)
- 可以看到實現了 DeferredImportSelector 接口,該接口繼承自 ImportSelector,根據
Javadoc
可知,多用於導入被 @Conditional 注解的Bean,之后會進行filter
操作 - AutoConfigurationImportSelector.AutoConfigurationGroup#process 方法,
SpringBoot
啟動時會調用該方法,進行自動裝配的處理,見SpringApplication#run(java.lang.String...)源碼解析- SpringApplication#run(java.lang.String...)
- SpringApplication#refreshContext(即 Spring IOC 容器初始化的過程中)
- ConfigurationClassParser#parse
- AutoConfigurationImportSelector.AutoConfigurationGroup#process
- 通過SpringFactoriesLoader#loadFactoryNames獲取應考慮的自動配置名稱,例如來源於
spring-boot-autoconfigure
jar包下的 META-INF/spring.factories 文件下的配置 - 通過 filter 過濾掉當前環境不需要自動裝配的類,各種 @Conditional 不滿足就被過濾掉
- 將需要自動裝配的全路徑類名注冊到
SpringIOC
容器,自此 SpringBoot 自動裝配完成!
- 可以看到實現了 DeferredImportSelector 接口,該接口繼承自 ImportSelector,根據
源碼
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
SpringBoot 自動裝配的核心注解,在 Spring
框架中就提供了各種以@Enable開頭的注解,例如: @EnableCircuitBreaker
、@EnableScheduling
等;
@EnableAutoConfiguration,借助@Import的支持,收集和注冊特定場景相關的bean定義;
自動裝配的類,通常是 @Configuration
類,通過 SpringFactoriesLoader
加載到 Spring
容器。
@AutoConfigurationPackage
注冊當前啟動類的根package;
注冊 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的BeanDefinition。
AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
-
可以看到實現了 DeferredImportSelector 接口,該接口繼承自ImportSelector,根據Javadoc可知,多用於導入被@Conditional注解的Bean
-
DeferredImportSelector接口中有個process方法,SpringBoot啟動時會調用該方法,進行自動裝配的處理,大體流程如下:
- SpringApplication#run(java.lang.String...)
- SpringApplication#refreshContext
- ConfigurationClassParser#parse
- AutoConfigurationImportSelector.AutoConfigurationGroup#process
-
AutoConfigurationImportSelector.AutoConfigurationGroup#process方法的源碼:
調用了AutoConfigurationImportSelector#getAutoConfigurationEntry方法,獲取需要自動裝配類
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())); // 獲取需要自動裝配類 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } }
-
AutoConfigurationImportSelector#getAutoConfigurationEntry大體流程如下:
-
通過SpringFactoriesLoader#loadFactoryNames獲取應考慮的自動配置名稱,例如來源於
spring-boot-autoconfigure
jar包下的META-INF/spring.factories文件下的配置 -
通過filter過濾掉當前環境不需要自動裝配的類,比如沒有集成RabbitMQ,就不需要,或者有的條件@Conditional不滿足也不需要自動裝配
-
返回需要自動裝配的全路徑類名
-
源碼如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); // 獲取預先定義的應考慮的自動配置類名稱 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); // 通過filter過濾掉當前環境不需要自動裝配的類,比如沒有集成RabbitMQ,就不需要,或者有的條件@Conditional不滿足也不需要自動裝配 configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); // 返回需要自動裝配的全路徑類名 return new AutoConfigurationEntry(configurations, exclusions); }
-
-
AutoConfigurationImportSelector#getCandidateConfigurations源碼如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
-
通過SpringFactoriesLoader#loadFactoryNames獲取應考慮的自動配置名稱,通過META-INF/spring.factories下的配置,例如:
spring-boot-autoconfigure jar包下的
spring.factories
文件: -
執行完
configurations = getConfigurationClassFilter().filter(configurations);
之后,各種@Conditional不滿足就被過濾掉,剩下35個了 -
可以通過如下方法進行驗證,結果沒有
RabbitAutoConfiguration
相關的Bean,拋出異常NoSuchBeanDefinitionException
@SpringBootApplication public class SpringDemosApplication implements ApplicationContextAware { private static ApplicationContext applicationContext; public static void main(String[] args) { SpringApplication.run(SpringDemosApplication.class, args); System.out.println(applicationContext.getBean(AopAutoConfiguration.class)); System.out.println(applicationContext.getBean(RabbitAutoConfiguration.class)); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringDemosApplication.applicationContext = applicationContext; } }
main方法
例子
main方法里調用org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)方法
@SpringBootApplication
public class SpringDemosApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDemosApplication.class, args);
}
}
SpringApplication#run
調用另外一個同名的重載方法run
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
實例化SpringApplication對象
- 首先會實例化SpringApplication一個對象
- 在構造方法里初始化一些屬性,比如webApplicationType,比如"SERVLET",初始化一些listeners
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 初始化webApplicationType,比如"SERVLET"
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 初始化一些listeners
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringApplication#run(java.lang.String...)源碼解析
經典的觀察者模式,只要你把事件廣播的順序理解了,那整個流程就很容易串起來了:
- 創建一個StopWatch實例,用來記錄SpringBoot的啟動時間
- 通過SpringFactoriesLoader加載listeners:比如EventPublishingRunListener
- 發布SprintBoot開始啟動事件(EventPublishingRunListener#starting())
- 創建和配置environment(environmentPrepared())
- 打印SpringBoot的banner和版本
- 創建對應的ApplicationContext:Web類型,Reactive類型,普通的類型(非Web)
- prepareContext
- 准備ApplicationContext,Initializers設置到ApplicationContext(contextPrepared())
- 打印啟動日志,打印profile信息(如dev, test, prod)
- 最終會調用到AbstractApplicationContext#refresh方法,實際上就是Spring IOC容器的創建過程,並且會進行自動裝配的操作,以及發布ApplicationContext已經refresh事件,標志着ApplicationContext初始化完成(contextLoaded())
- afterRefresh hook方法
- stopWatch停止計時,日志打印總共啟動的時間
- 發布SpringBoot程序已啟動事件(started())
- 調用ApplicationRunner和CommandLineRunner
- 最后發布就緒事件ApplicationReadyEvent,標志着SpringBoot可以處理就收的請求了(running())
public ConfigurableApplicationContext run(String... args) {
// 創建一個StopWatch實例,用來記錄SpringBoot的啟動時間
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 通過SpringFactoriesLoader加載listeners:比如EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 發布SprintBoot啟動事件:ApplicationStartingEvent
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 創建和配置environment,發布事件:SpringApplicationRunListeners#environmentPrepared
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印SpringBoot的banner和版本
Banner printedBanner = printBanner(environment);
// 創建對應的ApplicationContext:Web類型,Reactive類型,普通的類型(非Web)
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准備ApplicationContext,Initializers設置到ApplicationContext后發布事件:ApplicationContextInitializedEvent
// 打印啟動日志,打印profile信息(如dev, test, prod)
// 調用EventPublishingRunListener發布ApplicationContext加載完畢事件:ApplicationPreparedEvent
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 最終會調用到AbstractApplicationContext#refresh方法,實際上就是Spring IOC容器的創建過程,並且會進行自動裝配的操作
// 以及發布ApplicationContext已經refresh事件,標志着ApplicationContext初始化完成
refreshContext(context);
// hook方法
afterRefresh(context, applicationArguments);
// stopWatch停止計時,日志打印總共啟動的時間
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 發布SpringBoot程序已啟動事件ApplicationStartedEvent
listeners.started(context);
// 調用ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 最后發布就緒事件ApplicationReadyEvent,標志着SpringBoot可以處理就收的請求了
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
SpringBoot啟動事件
-
SpringApplicationRunListeners的唯一實現是EventPublishingRunListener;
-
整個SpringBoot的啟動,流程就是各種事件的發布,調用EventPublishingRunListener中的方法。
-
只要明白了EventPublishingRunListener中事件發布的流程,也就明白了SpringBoot啟動的大體流程
EventPublishingRunListener
方法說明如下:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
// SpringBoot啟動事件
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
// 創建和配置環境
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
// 准備ApplicationContext
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
this.initialMulticaster
.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
// 發布ApplicationContext已經refresh事件,標志着ApplicationContext初始化完成
@Override
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));
}
// SpringBoot已啟動事件
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
// "SpringBoot現在可以處理接受的請求"事件
@Override
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);
}
}
private static class LoggingErrorHandler implements ErrorHandler {
private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);
@Override
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener", throwable);
}
}
}
SpringIOC 容器初始化過程
由於現在大都是用SpringBoot開發,所以呢,Spring IOC
初始化的源碼,就是AnnotationConfigApplicationContext中的源碼,IOC的初始化就是該類實例創建的過程。
創建的過程(AnnotationConfigApplicationContext的構造方法),由於debug過這個源碼,我個人把它分為兩大步(暫時我先寫出我的總結,后續看是否有時間能寫一篇關於debug的過程):
- 給我們的Bean,創建與之對應的BeanDefinition,然后把他們放入ConcurrentHashMap(key:beanName和value:beanDefinition)中;BeanDefinition實際上包括一些Bean的信息,比如BeanName, Scope, 是否被@Primary注解修飾,是否是@Lazy,以及@Description等注解
- refresh()方法: 創建IOC需要的資源
- 初始化BeanFactory, set一些屬性,如BeanClassLoader,systemEnvironment
- 如果是SpringBoot程序,會調用方法進行自動裝配:AutoConfigurationImportSelector.AutoConfigurationGroup#process,見:@EnableAutoConfiguration的總結
- 注冊MessageSource,國際化相關的資源,到ApplicationContext
- 注冊ApplicationListener到ApplicationContext
- 實例化化lazy-init的Bean
- 最后,publish相關的事件,ApplicationContext 就初始化完成,整個IOC容器初始化完成(IOC容器的本質就是初始化BeanFactory和ApplicationContext),就可以從IOC容器中獲取Bean自動注入了