作者筆記倉庫:https://github.com/seazean/javanotes
歡迎各位關注我的筆記倉庫,clone 倉庫到本地後使用 Typora 閱讀效果更好。
如果大家只關注 SpringBoot 如何自動裝配,可以只看“註解分析”和“裝配流程”兩個小節
啟動流程
應用啟動:
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
// 啟動程式碼
SpringApplication.run(BootApplication.class, args);
}
}
SpringApplication 構造方法:
-
this.resourceLoader = resourceLoader
:資源載入器,初始為 null -
this.webApplicationType = WebApplicationType.deduceFromClasspath()
:判斷當前應用的型別,是響應式還是 Web 類 -
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories()
:獲取引導器- 去
META-INF/spring.factories
檔案中找 org.springframework.boot.Bootstrapper - 尋找的順序:classpath → spring-beans → boot-devtools → springboot → boot-autoconfigure
- 去
-
setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class))
:獲取初始化器- 去
META-INF/spring.factories
檔案中找 org.springframework.context.ApplicationContextInitializer
- 去
-
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))
:獲取監聽器- 去
META-INF/spring.factories
檔案中找 org.springframework.context.ApplicationListener
- 去
-
this.mainApplicationClass = deduceMainApplicationClass()
:獲取出 main 程式類
SpringApplication#run(String... args):
-
StopWatch stopWatch = new StopWatch()
:停止監聽器,監控整個應用的啟停 -
stopWatch.start()
:記錄應用的啟動時間 -
bootstrapContext = createBootstrapContext()
:建立引導上下文環境bootstrapContext = new DefaultBootstrapContext()
:建立預設的引導類環境this.bootstrapRegistryInitializers.forEach()
:遍歷所有的引導器呼叫 initialize 方法完成初始化設定
-
configureHeadlessProperty()
:讓當前應用進入 headless 模式 -
listeners = getRunListeners(args)
:獲取所有 RunListener(執行監聽器)- 去
META-INF/spring.factories
檔案中找 org.springframework.boot.SpringApplicationRunListener
- 去
-
listeners.starting(bootstrapContext, this.mainApplicationClass)
:遍歷所有的執行監聽器呼叫 starting 方法 -
applicationArguments = new DefaultApplicationArguments(args)
:獲取所有的命令列引數 -
environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments)
:準備環境-
environment = getOrCreateEnvironment()
:返回或建立基礎環境資訊物件switch (this.webApplicationType)
:根據當前應用的型別建立環境case SERVLET
:Web 應用環境對應 ApplicationServletEnvironmentcase REACTIVE
:響應式程式設計對應 ApplicationReactiveWebEnvironmentdefault
:預設為 Spring 環境 ApplicationEnvironment
-
configureEnvironment(environment, applicationArguments.getSourceArgs())
:讀取所有配置源的屬性值配置環境 -
ConfigurationPropertySources.attach(environment)
:屬性值繫結環境資訊sources.addFirst(ATTACHED_PROPERTY_SOURCE_NAME,..)
:把 configurationProperties 放入環境的屬性資訊頭部
-
listeners.environmentPrepared(bootstrapContext, environment)
:執行監聽器呼叫 environmentPrepared(),EventPublishingRunListener 釋出事件通知所有的監聽器當前環境準備完成 -
DefaultPropertiesPropertySource.moveToEnd(environment)
:移動 defaultProperties 屬性源到環境中的最後一個源 -
bindToSpringApplication(environment)
:與容器繫結當前環境 -
ConfigurationPropertySources.attach(environment)
:重新將屬性值繫結環境資訊-
sources.remove(ATTACHED_PROPERTY_SOURCE_NAME)
:從環境資訊中移除 configurationProperties -
sources.addFirst(ATTACHED_PROPERTY_SOURCE_NAME,..)
:把 configurationProperties 重新放入環境資訊
-
-
-
configureIgnoreBeanInfo(environment)
:配置忽略的 bean -
printedBanner = printBanner(environment)
:列印 SpringBoot 標誌 -
context = createApplicationContext()
:建立 IOC 容器switch (this.webApplicationType)
:根據當前應用的型別建立 IOC 容器case SERVLET
:Web 應用環境對應 AnnotationConfigServletWebServerApplicationContextcase REACTIVE
:響應式程式設計對應 AnnotationConfigReactiveWebServerApplicationContextdefault
:預設為 Spring 環境 AnnotationConfigApplicationContext
-
context.setApplicationStartup(this.applicationStartup)
:設定一個啟動器 -
prepareContext()
:配置 IOC 容器的基本資訊-
postProcessApplicationContext(context)
:後置處理流程 -
applyInitializers(context)
:獲取所有的初始化器呼叫 initialize() 方法進行初始化 -
listeners.contextPrepared(context)
:所有的執行監聽器呼叫 environmentPrepared() 方法,EventPublishingRunListener 釋出事件通知 IOC 容器準備完成 -
listeners.contextLoaded(context)
:所有的執行監聽器呼叫 contextLoaded() 方法,通知 IOC 載入完成
-
-
refreshContext(context)
:重新整理 IOC 容器- Spring 的容器啟動流程
invokeBeanFactoryPostProcessors(beanFactory)
:實現了自動裝配onRefresh()
:建立 WebServer 使用該介面
-
afterRefresh(context, applicationArguments)
:留給使用者自定義容器重新整理完成後的處理邏輯 -
stopWatch.stop()
:記錄應用啟動完成的時間 -
callRunners(context, applicationArguments)
:呼叫所有 runners -
listeners.started(context)
:所有的執行監聽器呼叫 started() 方法 -
listeners.running(context)
:所有的執行監聽器呼叫 running() 方法-
獲取容器中的 ApplicationRunner、CommandLineRunner
-
AnnotationAwareOrderComparator.sort(runners)
:合併所有 runner 並且按照 @Order 進行排序 -
callRunner()
:遍歷所有的 runner,呼叫 run 方法
-
-
handleRunFailure(context, ex, listeners)
:處理異常,出現異常進入該邏輯handleExitCode(context, exception)
:處理錯誤程式碼listeners.failed(context, exception)
:執行監聽器呼叫 failed() 方法reportFailure(getExceptionReporters(context), exception)
:通知異常
註解分析
SpringBoot 定義了一套介面規範,這套規範規定 SpringBoot 在啟動時會掃描外部引用 jar 包中的 META-INF/spring.factories
檔案,將檔案中配置的型別資訊載入到 Spring 容器,並執行類中定義的各種操作,對於外部的 jar 包,直接引入一個 starter 即可
@SpringBootApplication 註解是 @Configuration
、@EnableAutoConfiguration
、@ComponentScan
註解的集合
-
@SpringBootApplication 註解
@Inherited @SpringBootConfiguration //代表 @SpringBootApplication 擁有了該註解的功能 @EnableAutoConfiguration //同理 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) // 掃描被 @Component (@Service,@Controller)註解的 bean,容器中將排除TypeExcludeFilter 和 AutoConfigurationExcludeFilter public @interface SpringBootApplication { }
-
@SpringBootConfiguration 註解:
@Configuration // 代表是配置類 @Indexed public @interface SpringBootConfiguration { @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true; }
@AliasFor 註解:表示別名,可以註解到自定義註解的兩個屬性上表示這兩個互為別名,兩個屬性其實是同一個含義相互替代
-
@ComponentScan 註解:預設掃描當前包及其子級包下的所有檔案
-
@EnableAutoConfiguration 註解:啟用 SpringBoot 的自動配置機制
@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
-
@AutoConfigurationPackage:將新增該註解的類所在的 package 作為自動配置 package 進行管理,把啟動類所在的包設定一次,為了給各種自動配置的第三方庫掃描用,比如帶 @Mapper 註解的類,Spring 自身是不能識別的,但自動配置的 Mybatis 需要掃描用到,而 ComponentScan 只是用來掃描註解類,並沒有提供介面給三方使用
@Import(AutoConfigurationPackages.Registrar.class) // 利用 Registrar 給容器中匯入元件 public @interface AutoConfigurationPackage { String[] basePackages() default {}; //自動配置包,指定了配置類的包 Class<?>[] basePackageClasses() default {}; }
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]))
:註冊 BDnew PackageImports(metadata).getPackageNames()
:獲取新增當前註解的類的所在包registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames))
:存放到容器中new BasePackagesBeanDefinition(packageNames)
:把當前主類所在的包名封裝到該物件中
-
@Import(AutoConfigurationImportSelector.class):首先自動裝配的核心類
容器重新整理時執行:invokeBeanFactoryPostProcessors() → invokeBeanDefinitionRegistryPostProcessors() → postProcessBeanDefinitionRegistry() → processConfigBeanDefinitions() → parse() → process() → processGroupImports() → getImports() → process() → AutoConfigurationImportSelector#getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 獲取註解屬性,@SpringBootApplication 註解的 exclude 屬性和 excludeName 屬性 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 = getConfigurationClassFilter().filter(configurations); // 獲取 AutoConfigurationImportListener 類的監聽器呼叫 onAutoConfigurationImportEvent 方法 fireAutoConfigurationImportEvents(configurations, exclusions); // 包裝成 AutoConfigurationEntry 返回 return new AutoConfigurationEntry(configurations, exclusions); }
AutoConfigurationImportSelector#getCandidateConfigurations:獲取自動配置的候選項
-
List<String> configurations = SpringFactoriesLoader.loadFactoryNames()
:載入自動配置類引數一:
getSpringFactoriesLoaderFactoryClass()
獲取 @EnableAutoConfiguration 註解類引數二:
getBeanClassLoader()
獲取類載入器factoryTypeName = factoryType.getName()
:@EnableAutoConfiguration 註解的全類名return loadSpringFactories(classLoaderToUse).getOrDefault()
:載入資源urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
:獲取資源類FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
:載入的資源的位置
-
return configurations
:返回所有自動裝配類的候選項
-
-
從 spring-boot-autoconfigure-2.5.3.jar/META-INF/spring.factories 檔案中獲取自動裝配類,進行條件裝配,按需裝配
-
裝配流程
Spring Boot 通過 @EnableAutoConfiguration
開啟自動裝配,通過 SpringFactoriesLoader 載入 META-INF/spring.factories
中的自動配置類實現自動裝配,自動配置類其實就是通過 @Conditional
註解按需載入的配置類(JVM 類載入機制),想要其生效必須引入 spring-boot-starter-xxx
包實現起步依賴
- SpringBoot 先載入所有的自動配置類 xxxxxAutoConfiguration
- 每個自動配置類進行條件裝配,預設都會繫結配置檔案指定的值(xxxProperties 和配置檔案進行了繫結)
- SpringBoot 預設會在底層配好所有的元件,如果使用者自己配置了以使用者的優先
- 定製化配置:
- 使用者可以使用 @Bean 新建自己的元件來替換底層的元件
- 使用者可以去看這個元件是獲取的配置檔案字首值,在配置檔案中修改
以 DispatcherServletAutoConfiguration 為例:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 類中的 Bean 預設不是單例
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
// 條件裝配,環境中有 DispatcherServlet 類才進行自動裝配
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
// 註冊的 DispatcherServlet 的 BeanName
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
// 繫結配置檔案的屬性,從配置檔案中獲取配置項
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
// 給容器註冊一個 DispatcherServlet,起名字為 dispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
// 新建一個 DispatcherServlet 設定相關屬性
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// spring.mvc 中的配置項獲取注入,沒有就填充預設值
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
// ......
// 返回該物件註冊到容器內
return dispatcherServlet;
}
@Bean
// 容器中有這個型別元件才進行裝配
@ConditionalOnBean(MultipartResolver.class)
// 容器中沒有這個名字 multipartResolver 的元件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
// 方法名就是 BeanName
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 給 @Bean 標註的方法傳入了物件引數,這個引數就會從容器中找,因為使用者自定義了該型別,以使用者配置的優先
// 但是名字不符合規範,所以獲取到該 Bean 並返回到容器一個規範的名稱:multipartResolver
return resolver;
}
}
}
//將配置檔案中的 spring.mvc 字首的屬性與該類繫結
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties { }
內嵌容器
(補充內容,WebServer)
SpringBoot 嵌入式 Servlet 容器,預設支援的 webServe:Tomcat、Jetty、Undertow
配置方式:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion> <!--必須要把內嵌的 Tomcat 容器-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
建立 Web 容器:
-
SpringApplication.run(BootApplication.class, args)
:應用啟動 -
ConfigurableApplicationContext.run()
:-
context = createApplicationContext()
:建立容器-
applicationContextFactory = ApplicationContextFactory.DEFAULT
ApplicationContextFactory DEFAULT = (webApplicationType) -> { try { switch (webApplicationType) { case SERVLET: // Servlet 容器,繼承自 ServletWebServerApplicationContext return new AnnotationConfigServletWebServerApplicationContext(); case REACTIVE: // 響應式程式設計 return new AnnotationConfigReactiveWebServerApplicationContext(); default: // 普通 Spring 容器 return new AnnotationConfigApplicationContext(); } } catch (Exception ex) { throw new IllegalStateException(); } }
-
applicationContextFactory.create(this.webApplicationType)
:根據應用型別建立容器
-
-
refreshContext(context)
:容器啟動
-
內嵌容器工作流程:
- Web 應用啟動,SpringBoot 匯入 Web 場景包 tomcat,建立一個 Web 版的 IOC 容器 ServletWebServerApplicationContext
-
ServletWebServerApplicationContext 容器啟動時進入 refresh() 邏輯,Spring 容器啟動邏輯中,在例項化非懶載入的單例 Bean 之前有一個方法 onRefresh(),留給子類去擴充套件,該容器就是重寫這個方法建立 WebServer
protected void onRefresh() { //省略.... createWebServer(); } private void createWebServer() { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); createWebServer.end(); }
獲取 WebServer 工廠 ServletWebServerFactory,並且獲取的數量不等於 1 會報錯,Spring 底層有三種:
TomcatServletWebServerFactory
、JettyServletWebServerFactory
、UndertowServletWebServerFactory
-
自動配置類 ServletWebServerFactoryAutoConfiguration 匯入了 ServletWebServerFactoryConfiguration(配置類),根據條件裝配判斷系統中到底匯入了哪個 Web 伺服器的包,建立出伺服器並啟動
-
預設是 web-starter 匯入 tomcat 包,容器中就有 TomcatServletWebServerFactory,建立出 Tomcat 伺服器並啟動,
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { // 初始化 initialize(); }
初始化方法 initialize 中有啟動方法:
this.tomcat.start()