前言
大家都知道springboot為我們做的自動配置,很方便快捷,但是不懂springboot內部啟動原理,以后難免會吃虧,接下來我們一起一步步揭開springboot的神秘面紗,讓它不在神秘。
原博客地址:https://www.cnblogs.com/shamo89/p/8184960.html
正文
當我們開發任何一個springboot項目,都會用到如下的啟動類
@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 { ...... }
雖然定義使用了多個Annotation進行了原信息標注,但實際上重要的只有三個Annotation:
- @Configuration(@SpringBootConfiguration點開查看發現里面還是應用了@Configuration)
- @EnableAutoConfiguration
- @ComponentScan
所以,如果我們使用如下的SpringBoot啟動類,整個SpringBoot應用依然可以與之前的啟動類功能對等:
@Configuration @EnableAutoConfiguration @ComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
每次寫這三個比較累,所以寫一個@SpringBootApplication方便點。接下來分別介紹這三個Annotation。
@Configuration
這里的@Configuration對我們來說不陌生,他就是JavaConfig形式的Spring IOC容器的配置類使用的那個@Configuration,SpringBoot社區推薦使用基於JavaConfig的配置形式,所以,這里的啟動類標注了@Configuration之后,本身其實也就是一個IOC容器的配置類。
舉幾個例子回顧一下,XML跟config配置方式的區別:
- 表達形式層面
基於xml配置的方式是這樣:
<?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定義--> </beans>
而基於JavaConfig的配置方式是這樣:
@Configuration public class MockConfiguration{ //bean定義 }
任何一個標注了@Configuration的Java類定義都是一個JavaConfig配置類。
- 注冊bean定義層面
基於XML的配置形式是這樣:
<bean id="mockService" class="..MockServiceImpl"> ... </bean>
而基於JavaConfig的配置形式是這樣的:
@Configuration public class MockConfiguration{ @Bean public MockService mockService(){ return new MockServiceImpl(); } }
任何一個標注了@Bean的方法,其返回值作為一個bean定義注冊到Spring的IoC容器,方法名將默認成該bean定義的id。
- 表達依賴注入關系層面
為了表達bean與bean之間的依賴關系,在XML形式中一般是這樣:
<bean id="mockService" class="..MockServiceImpl"> <propery name ="dependencyService" ref="dependencyService" /> </bean> <bean id="dependencyService" class="DependencyServiceImpl"></bean>
而基於JavaConfig的配置形式是這樣的:
@Configuration public class MockConfiguration{ @Bean public MockService mockService(){ return new MockServiceImpl(dependencyService()); } @Bean public DependencyService dependencyService(){ return new DependencyServiceImpl(); } }
如果一個bean的定義依賴其他bean,則直接調用對應的JavaConfig類中依賴bean的創建方法就可以了。
@ComponentScan
@ComponentScan這個注解在spring中很重要,它對應XML配置中的元素,@ComponentScan的功能其實就是自動掃描並加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終通過注解@EnableAutoConfiguration將這些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:導入自動配置的組件
AutoConfiguratioPackage注解:
1 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { 2 3 @Override 4 public void registerBeanDefinitions(AnnotationMetadata metadata, 5 BeanDefinitionRegistry registry) { 6 register(registry, new PackageImport(metadata).getPackageName()); 7 } 8 9 @Override 10 public Set<Object> determineImports(AnnotationMetadata metadata) { 11 return Collections.<Object>singleton(new PackageImport(metadata)); 12 } 13 14 }
它其實是注冊了一個Bean的定義。
new PackageImport(metadata).getPackageName(),他其實返回了當前主程序類的同級以及子級的包組件。
以上圖為例,DemoApplication是和demo包同級,但是demo2這個類是DemoApplication的父級,和example包同級也就是說,DemoApplication啟動加載的bean中,並不會加載demo2,這也就是為什么,我們要把DemoApplication放在項目的最高級中。
Import(AutoConfigurationImportSelector.class)注解:
可以從圖中看出AutoConfigurationImportSelector繼承了DeferredImportSelector接口繼承了ImportSelector接口
實現了ImportSelector接口的selectImports方法。
1 @Override 2 public String[] selectImports(AnnotationMetadata annotationMetadata) { 3 if (!isEnabled(annotationMetadata)) { 4 return NO_IMPORTS; 5 } 6 try { 7 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader 8 .loadMetadata(this.beanClassLoader); 9 AnnotationAttributes attributes = getAttributes(annotationMetadata); 10 List<String> configurations = getCandidateConfigurations(annotationMetadata, 11 attributes); 12 configurations = removeDuplicates(configurations); 13 configurations = sort(configurations, autoConfigurationMetadata); 14 Set<String> exclusions = getExclusions(annotationMetadata, attributes); 15 checkExcludedClasses(configurations, exclusions); 16 configurations.removeAll(exclusions); 17 configurations = filter(configurations, autoConfigurationMetadata); 18 fireAutoConfigurationImportEvents(configurations, exclusions); 19 return configurations.toArray(new String[configurations.size()]); 20 } 21 catch (IOException ex) { 22 throw new IllegalStateException(ex); 23 } 24 }
可以看到第十行getCandidateConfigurations方法,他其實是使用SpringFactoriesLoader去加載public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。這個外部文件,有很多自動配置的類。如下:
其中,最關鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助Springboot應用將所有符合條件的@Configuration配置都加載到當前SpringSpringboot創建並使用的IoC容器。就像一只“八爪魚”。
自動配置幕后英雄:SpringFactoriesLoader詳解
借助於Spring框架原有的一個工具類:SpringFactoryLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!
SpringFactoryLoader屬於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類
上圖就是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置文件中摘錄的一段內容,可以很好地說明問題。
所以,@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實例的run方法。在SpringApplication實例初始化的時候,會提前做幾件事:
1 @SuppressWarnings({ "unchecked", "rawtypes" }) 2 private void initialize(Object[] sources) { 3 if (sources != null && sources.length > 0) { 4 this.sources.addAll(Arrays.asList(sources)); 5 } 6 this.webEnvironment = deduceWebEnvironment(); 7 setInitializers((Collection) getSpringFactoriesInstances( 8 ApplicationContextInitializer.class)); 9 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 10 this.mainApplicationClass = deduceMainApplicationClass(); 11 }
- 第6行根據classpath里面是否存在某個特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該創建一個為Web應用使用的ApplicationContext類型。
- 第7行使用SpringFactoriesLoader在應用的classpath中查找並加載所有可用的ApplicationContextInitializer。
- 第8行使用SpringFactoriesLoader在應用classpath中查找並加載所有可用的ApplicationListener。
- 第10行判斷並設置main方法的定義類。
2)SpringApplication實例初始化完成並且完成設置后,就開始執行run方法的邏輯了,方法執行開始,首先遍歷執行所有通過SpringFactoriesLoader可以查找並加載的SpringApplicationRunListener。調用他們的started()方法,告訴這些SpringApplicationRunListener,Springboot應用可以開始執行了。
1 public ConfigurableApplicationContext run(String... args) { 2 StopWatch stopWatch = new StopWatch(); 3 stopWatch.start(); 4 ConfigurableApplicationContext context = null; 5 FailureAnalyzers analyzers = null; 6 configureHeadlessProperty(); 7 SpringApplicationRunListeners listeners = getRunListeners(args); 8 listeners.starting(); 9 try { 10 ApplicationArguments applicationArguments = new DefaultApplicationArguments( 11 args); 12 ConfigurableEnvironment environment = prepareEnvironment(listeners, 13 applicationArguments); 14 Banner printedBanner = printBanner(environment); 15 context = createApplicationContext(); 16 analyzers = new FailureAnalyzers(context); 17 prepareContext(context, environment, listeners, applicationArguments, 18 printedBanner); 19 //核心點:會打印springboot啟動標志,直到server.port端口啟動 20 refreshContext(context); 21 afterRefresh(context, applicationArguments); 22 listeners.finished(context, null); 23 stopWatch.stop(); 24 if (this.logStartupInfo) { 25 new StartupInfoLogger(this.mainApplicationClass) 26 .logStarted(getApplicationLog(), stopWatch); 27 } 28 return context; 29 } 30 catch (Throwable ex) { 31 handleRunFailure(context, listeners, analyzers, ex); 32 throw new IllegalStateException(ex); 33 } 34 }
3)創建並配置當前Spring Boot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。
1 private ConfigurableEnvironment prepareEnvironment( 2 SpringApplicationRunListeners listeners, 3 ApplicationArguments applicationArguments) { 4 // Create and configure the environment 5 ConfigurableEnvironment environment = getOrCreateEnvironment(); 6 configureEnvironment(environment, applicationArguments.getSourceArgs()); 7 listeners.environmentPrepared(environment); 8 if (!this.webEnvironment) { 9 environment = new EnvironmentConverter(getClassLoader()) 10 .convertToStandardEnvironmentIfNecessary(environment); 11 } 12 return environment; 13 }
4)遍歷調用所有SpringApplicationRunListener的environmentPrepared的方法,告訴他們:“當前SPringBoot應用的Environment准備好了”。
1 public void environmentPrepared(ConfigurableEnvironment environment) { 2 for (SpringApplicationRunListener listener : this.listeners) { 3 listener.environmentPrepared(environment); 4 } 5 }
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然后遍歷調用這些ApplicationContextInitialzer的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只是在這些概念和實踐上對特定的場景事先進行了固化和升華,而也恰恰是這些固化讓我們開發基於Spring框架的應用更加方便高效。