一段眾所周知的代碼
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在這段眾所周知的代碼里,最能吸引我的便是Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)。然而這兩貨確實是springboot啟動的入口和關鍵。
SpringBootApplication背后的那些事
@Target(ElementType.TYPE) // 注解的適用范圍,其中TYPE用于描述類、接口(包括包注解類型)或enum聲明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三個生命周期)
@Documented // 表明這個注解應該被javadoc記錄
@Inherited // 子類可以繼承該注解
@SpringBootConfiguration // 繼承了Configuration,表示當前是注解類
@EnableAutoConfiguration // 開啟springboot的注解功能,springboot的四大神器之一,其借助@import的幫助
@ComponentScan(excludeFilters = { // 掃描路徑設置(具體使用待確認)
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
我們可以看到,在SpringbootApplication上有SpringbootConfiguration,EnableAutoConfiguration,ComponentScan三個注解,所以那段眾所周知的代碼實際上等價于
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Configuration說明
這里的@Configuration對我們來說不陌生,它就是JavaConfig形式的Spring Ioc容器的配置類使用的那個@Configuration,SpringBoot社區推薦使用基于JavaConfig的配置形式,所以,這里的啟動類標注了@Configuration之后,本身其實也是一個IoC容器的配置類。
舉幾個簡單例子回顧下,XML跟config配置方式的區別:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-lazy-init="true">
<!--bean定義-->
<bean id="mockService" class="..MockServiceImpl">
<propery name ="dependencyService" ref="dependencyService" />
</bean>
<bean id="dependencyService" class="DependencyServiceImpl"></bean>
</beans>
而基于JavaConfig的配置方式是這樣:
@Configuration
public class MockConfiguration{
//或者放在方法參數,都是可以的,如果依賴的比較多,參數會很長,如果依賴的比較少,放在參數里比較簡單
@Bean
public MockService mockService(){
return new MockServiceImpl(dependencyService());
}
@Bean
public DependencyService dependencyService(){
return new DependencyServiceImpl();
}
}
ComponentScan說明
@ComponentScan這個注解在Spring中很重要,它對應XML配置中的元素,@ComponentScan的功能其實就是自動掃描并加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。
我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描。
注:所以SpringBoot的啟動類最好是放在root package下,因為默認不指定basePackages。
EnableAutoConfiguration說明
個人感覺@EnableAutoConfiguration這個Annotation最為重要,所以放在最后來解讀,大家是否還記得Spring框架提供的各種名字為@Enable開頭的Annotation定義?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其實一脈相承,簡單概括一下就是,借助@Import的支持,收集和注冊特定場景相關的bean定義。
- @EnableScheduling是通過@Import將Spring調度框架相關的bean定義都加載到IoC容器。
- @EnableMBeanExport是通過@Import將JMX相關的bean定義加載到IoC容器。
而@EnableAutoConfiguration也是借助@Import的幫助,將所有符合自動配置條件的bean定義加載到IoC容器,僅此而已!
@EnableAutoConfiguration作為一個復合Annotation,其自身定義關鍵信息如下:
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
兩個比較重要的注解:
- @AutoConfigurationPackage:自動配置包
- @Import: 導入自動配置的組件
AutoConfigurationPackage說明
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
它其實是注冊了一個Bean的定義。
new PackageImport(metadata).getPackageName(),它其實返回了當前主程序類的 同級以及子級的包組件。
Import(AutoConfigurationImportSelector.class)注解:
可以從圖中看出 AutoConfigurationImportSelector 繼承了 DeferredImportSelector 繼承了 ImportSelector
ImportSelector有一個方法為:selectImports。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
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);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
可以看到它其實是去加載 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。這個外部文件,有很多自動配置的類。如下:
其中,最關鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建并使用的IoC容器。就像一只“八爪魚”一樣。
自動配置幕后英雄:SpringFactoriesLoader詳解
借助于Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!
SpringFactoriesLoader屬于Spring框架私有的一種擴展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置。
public abstract class SpringFactoriesLoader {
//...
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
...
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
....
}
}
配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對應的一組@Configuration類。
所以,@EnableAutoConfiguration自動配置的魔法騎士就變成了:從classpath中搜尋所有的META-INF/spring.factories配置文件,并將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對應的配置項通過反射(Java Refletion)實例化為對應的標注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個并加載到IoC容器。
Springboot啟動圖
深入探索SpringApplication執行流程
SpringApplication的run方法的實現是我們本次旅程的主要線路,該方法的主要流程大體可以歸納如下:
1) 如果我們使用的是SpringApplication的靜態run方法,那么,這個方法里面首先要創建一個SpringApplication對象實例,然后調用這個創建好的SpringApplication的實例方法。在SpringApplication實例初始化的時候,它會提前做幾件事情:
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
- 根據classpath里面是否存在某個特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該創建一個為Web應用使用的ApplicationContext類型。
- 使用SpringFactoriesLoader在應用的classpath中查找并加載所有可用的ApplicationContextInitializer。
- 使用SpringFactoriesLoader在應用的classpath中查找并加載所有可用的ApplicationListener。
- 推斷并設置main方法的定義類。
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2) SpringApplication實例初始化完成并且完成設置后,就開始執行run方法的邏輯了,方法執行伊始,首先遍歷執行所有通過SpringFactoriesLoader可以查找到并加載的SpringApplicationRunListener。調用它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應用要開始執行咯!”。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 核心點:會打印springboot的啟動標志,直到server.port端口啟動
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
3) 創建并配置當前Spring Boot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
4) 遍歷調用所有SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:“當前SpringBoot應用使用的Environment準備好了咯!”。
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
5) 如果SpringApplication的showBanner屬性被設置為true,則打印banner。
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
6) 根據用戶是否明確設置了applicationContextClass類型以及初始化階段的推斷結果,決定該為當前SpringBoot應用創建什么類型的ApplicationContext并創建完成,然后根據條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當然,最重要的,將之前準備好的Environment設置給創建好的ApplicationContext使用。
7) ApplicationContext創建好之后,SpringApplication會再次借助Spring-FactoriesLoader,查找并加載classpath中所有可用的ApplicationContext-Initializer,然后遍歷調用這些ApplicationContextInitializer的initialize(applicationContext)方法來對已經創建好的ApplicationContext進行進一步的處理。
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
8) 遍歷調用所有SpringApplicationRunListener的contextPrepared()方法。
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
listeners.contextLoaded(context);
}
9) 最核心的一步,將之前通過@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經準備完畢的ApplicationContext。
private void prepareAnalyzer(ConfigurableApplicationContext context,
FailureAnalyzer analyzer) {
if (analyzer instanceof BeanFactoryAware) {
((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory());
}
}
10) 遍歷調用所有SpringApplicationRunListener的contextLoaded()方法。
public void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);
}
}
11) 調用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
12) 查找當前ApplicationContext中是否注冊有CommandLineRunner,如果有,則遍歷執行它們。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
13) 正常情況下,遍歷執行SpringApplicationRunListener的finished()方法、(如果整個過程出現異常,則依然調用所有SpringApplicationRunListener的finished()方法,只不過這種情況下會將異常信息一并傳入處理)
去除事件通知點后,整個流程如下:
public void finished(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFinishedListener(listener, context, exception);
}
}
到此,SpringBoot的核心組件完成了基本的解析,綜合來看,大部分都是Spring框架背后的一些概念和實踐方式,SpringBoot只是在這些概念和實踐上對特定的場景事先進行了固化和升華,而也恰恰是這些固化讓我們開發基于Sping框架的應用更加方便高效。