SpringBoot 原理深入以及源碼分析
我們都知道,Spring
有它強大的地方,也有它繁瑣的地方,畢竟如日中天的Spring
全家桶太強大了,所以導致依賴各種JAR
包維護起來費勁,還有編寫各種XML
配置文件。這兩個痛點SpringBoot
可以優雅的實現解決。背後當然是SpringBoot
的約定優於配置(Convention over Configuration)
,又稱按約定編程
,是一種軟件設計範式
。SpringBoot
是所有基於Spring
開發的項目的起點。SpringBoot
的設計就是爲了讓你儘可能快的跑起來Spring
應用程序並且儘可能減少你的配置文件。
瞭解了SpringBoot
出現的背景及優勢後,我們就開始從下面幾個方面來分析SpringBoot
的原理以及源碼分析。
一、依賴管理
問題1:爲什麼導入dependency時不需要指定版本?
在SpringBoot入門程序中,項目pom.xml文件中有兩個核心依賴:
spring-boot-starter-parent
spring-boot-starter-web
1、spring-boot-starter-parent
<!--SpringBoot 父項目依賴管理-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
我們再使用“ctrl+鼠標左鍵
”進入並查看spring-boot-starter-parent
底層源文件,發現spring-boot-starter-parent的底層有一個父依賴spring-boot-dependencies
,如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.1.RELEASE</version>
</parent>
繼續查看spring-boot-dependencies
的源文件:
<properties>
<activemq.version>5.15.12</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.80</appengine-sdk.version>
<artemis.version>2.12.0</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
<assertj.version>3.16.1</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.3</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
<build-helper-maven-plugin.version>3.1.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.10.11</byte-buddy.version>
<caffeine.version>2.8.4</caffeine.version>
<cassandra-driver.version>4.6.1</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
<commons-codec.version>1.14</commons-codec.version>
<commons-dbcp2.version>2.7.0</commons-dbcp2.version>
<commons-lang3.version>3.10</commons-lang3.version>
<commons-pool.version>1.6</commons-pool.version>
<commons-pool2.version>2.8.0</commons-pool2.version>
<couchbase-client.version>3.0.5</couchbase-client.version>
<db2-jdbc.version>11.5.0.0</db2-jdbc.version>
<dependency-management-plugin.version>1.0.9.RELEASE</dependency-management-plugin.version>
<derby.version>10.14.2.0</derby.version>
<dropwizard-metrics.version>4.1.9</dropwizard-metrics.version>
<ehcache.version>2.10.6</ehcache.version>
<ehcache3.version>3.8.1</ehcache3.version>
<elasticsearch.version>7.6.2</elasticsearch.version>
<embedded-mongo.version>2.2.0</embedded-mongo.version>
<exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
<flatten-maven-plugin.version>1.2.2</flatten-maven-plugin.version>
<flyway.version>6.4.4</flyway.version>
<freemarker.version>2.3.30</freemarker.version>
<git-commit-id-plugin.version>3.0.1</git-commit-id-plugin.version>
<glassfish-el.version>3.0.3</glassfish-el.version>
<glassfish-jaxb.version>2.3.3</glassfish-jaxb.version>
<groovy.version>2.5.12</groovy.version>
<gson.version>2.8.6</gson.version>
<h2.version>1.4.200</h2.version>
<hamcrest.version>2.2</hamcrest.version>
<hazelcast.version>3.12.7</hazelcast.version>
<hazelcast-hibernate5.version>1.3.2</hazelcast-hibernate5.version>
<hibernate.version>5.4.17.Final</hibernate.version>
<hibernate-validator.version>6.1.5.Final</hibernate-validator.version>
<hikaricp.version>3.4.5</hikaricp.version>
<hsqldb.version>2.5.0</hsqldb.version>
<htmlunit.version>2.40.0</htmlunit.version>
<httpasyncclient.version>4.1.4</httpasyncclient.version>
<httpclient.version>4.5.12</httpclient.version>
<httpcore.version>4.4.13</httpcore.version>
<infinispan.version>10.1.8.Final</infinispan.version>
<influxdb-java.version>2.18</influxdb-java.version>
<jackson-bom.version>2.11.0</jackson-bom.version>
<jakarta-activation.version>1.2.2</jakarta-activation.version>
<jakarta-annotation.version>1.3.5</jakarta-annotation.version>
<jakarta-jms.version>2.0.3</jakarta-jms.version>
<jakarta-json.version>1.1.6</jakarta-json.version>
<jakarta-json-bind.version>1.0.2</jakarta-json-bind.version>
<jakarta-mail.version>1.6.5</jakarta-mail.version>
<jakarta-persistence.version>2.2.3</jakarta-persistence.version>
<jakarta-servlet.version>4.0.3</jakarta-servlet.version>
<jakarta-servlet-jsp-jstl.version>1.2.7</jakarta-servlet-jsp-jstl.version>
<jakarta-transaction.version>1.3.3</jakarta-transaction.version>
<jakarta-validation.version>2.0.2</jakarta-validation.version>
<jakarta-websocket.version>1.1.2</jakarta-websocket.version>
<jakarta-ws-rs.version>2.1.6</jakarta-ws-rs.version>
<jakarta-xml-bind.version>2.3.3</jakarta-xml-bind.version>
<jakarta-xml-soap.version>1.4.2</jakarta-xml-soap.version>
<jakarta-xml-ws.version>2.3.3</jakarta-xml-ws.version>
<janino.version>3.1.2</janino.version>
<javax-activation.version>1.2.0</javax-activation.version>
<javax-annotation.version>1.3.2</javax-annotation.version>
<javax-cache.version>1.1.1</javax-cache.version>
<javax-jaxb.version>2.3.1</javax-jaxb.version>
<javax-jaxws.version>2.3.1</javax-jaxws.version>
<javax-jms.version>2.0.1</javax-jms.version>
<javax-json.version>1.1.4</javax-json.version>
<javax-jsonb.version>1.0</javax-jsonb.version>
<javax-mail.version>1.6.2</javax-mail.version>
<javax-money.version>1.0.3</javax-money.version>
<javax-persistence.version>2.2</javax-persistence.version>
<javax-transaction.version>1.3</javax-transaction.version>
<javax-validation.version>2.0.1.Final</javax-validation.version>
<javax-websocket.version>1.1</javax-websocket.version>
<jaxen.version>1.2.0</jaxen.version>
<jaybird.version>3.0.9</jaybird.version>
<jboss-logging.version>3.4.1.Final</jboss-logging.version>
<jboss-transaction-spi.version>7.6.0.Final</jboss-transaction-spi.version>
<jdom2.version>2.0.6</jdom2.version>
<jedis.version>3.3.0</jedis.version>
<jersey.version>2.30.1</jersey.version>
<jetty-el.version>8.5.54</jetty-el.version>
<jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
<jetty-reactive-httpclient.version>1.1.3</jetty-reactive-httpclient.version>
<jetty.version>9.4.29.v20200521</jetty.version>
<jmustache.version>1.15</jmustache.version>
<johnzon.version>1.2.7</johnzon.version>
<jolokia.version>1.6.2</jolokia.version>
<jooq.version>3.13.2</jooq.version>
<json-path.version>2.4.0</json-path.version>
<jsonassert.version>1.5.0</jsonassert.version>
<jstl.version>1.2</jstl.version>
<jtds.version>1.3.1</jtds.version>
<junit.version>4.13</junit.version>
<junit-jupiter.version>5.6.2</junit-jupiter.version>
<kafka.version>2.5.0</kafka.version>
<kotlin.version>1.3.72</kotlin.version>
<kotlin-coroutines.version>1.3.7</kotlin-coroutines.version>
<lettuce.version>5.3.1.RELEASE</lettuce.version>
<liquibase.version>3.8.9</liquibase.version>
<log4j2.version>2.13.3</log4j2.version>
<logback.version>1.2.3</logback.version>
<lombok.version>1.18.12</lombok.version>
<mariadb.version>2.6.0</mariadb.version>
<maven-antrun-plugin.version>1.8</maven-antrun-plugin.version>
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-dependency-plugin.version>3.1.2</maven-dependency-plugin.version>
<maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
<maven-enforcer-plugin.version>3.0.0-M3</maven-enforcer-plugin.version>
<maven-failsafe-plugin.version>2.22.2</maven-failsafe-plugin.version>
<maven-help-plugin.version>3.2.0</maven-help-plugin.version>
<maven-install-plugin.version>2.5.2</maven-install-plugin.version>
<maven-invoker-plugin.version>3.2.1</maven-invoker-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-javadoc-plugin.version>3.2.0</maven-javadoc-plugin.version>
<maven-resources-plugin.version>3.1.0</maven-resources-plugin.version>
<maven-shade-plugin.version>3.2.2</maven-shade-plugin.version>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<maven-war-plugin.version>3.2.3</maven-war-plugin.version>
<micrometer.version>1.5.1</micrometer.version>
<mimepull.version>1.9.13</mimepull.version>
<mockito.version>3.3.3</mockito.version>
<mongodb.version>4.0.4</mongodb.version>
<mssql-jdbc.version>7.4.1.jre8</mssql-jdbc.version>
<mysql.version>8.0.20</mysql.version>
<nekohtml.version>1.9.22</nekohtml.version>
<neo4j-ogm.version>3.2.12</neo4j-ogm.version>
<netty.version>4.1.50.Final</netty.version>
<netty-tcnative.version>2.0.31.Final</netty-tcnative.version>
<nio-multipart-parser.version>1.1.0</nio-multipart-parser.version>
<oauth2-oidc-sdk.version>7.1.1</oauth2-oidc-sdk.version>
<ojdbc.version>19.3.0.0</ojdbc.version>
<okhttp3.version>3.14.9</okhttp3.version>
<oracle-database.version>19.3.0.0</oracle-database.version>
<pooled-jms.version>1.1.1</pooled-jms.version>
<postgresql.version>42.2.14</postgresql.version>
<prometheus-pushgateway.version>0.9.0</prometheus-pushgateway.version>
<quartz.version>2.3.2</quartz.version>
<querydsl.version>4.3.1</querydsl.version>
<r2dbc-bom.version>Arabba-SR4</r2dbc-bom.version>
<rabbit-amqp-client.version>5.9.0</rabbit-amqp-client.version>
<reactive-streams.version>1.0.3</reactive-streams.version>
<reactor-bom.version>Dysprosium-SR8</reactor-bom.version>
<rest-assured.version>3.3.0</rest-assured.version>
<rsocket.version>1.0.1</rsocket.version>
<rxjava.version>1.3.8</rxjava.version>
<rxjava-adapter.version>1.2.1</rxjava-adapter.version>
<rxjava2.version>2.2.19</rxjava2.version>
<spring-boot.version>2.3.1.RELEASE</spring-boot.version>
<saaj-impl.version>1.5.2</saaj-impl.version>
<selenium.version>3.141.59</selenium.version>
<selenium-htmlunit.version>2.40.0</selenium-htmlunit.version>
<sendgrid.version>4.4.8</sendgrid.version>
<servlet-api.version>4.0.1</servlet-api.version>
<slf4j.version>1.7.30</slf4j.version>
<snakeyaml.version>1.26</snakeyaml.version>
<solr.version>8.5.1</solr.version>
<spring-amqp.version>2.2.7.RELEASE</spring-amqp.version>
<spring-batch.version>4.2.4.RELEASE</spring-batch.version>
<spring-data-releasetrain.version>Neumann-SR1</spring-data-releasetrain.version>
<spring-framework.version>5.2.7.RELEASE</spring-framework.version>
<spring-hateoas.version>1.1.0.RELEASE</spring-hateoas.version>
<spring-integration.version>5.3.1.RELEASE</spring-integration.version>
<spring-kafka.version>2.5.2.RELEASE</spring-kafka.version>
<spring-ldap.version>2.3.3.RELEASE</spring-ldap.version>
<spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
<spring-retry.version>1.2.5.RELEASE</spring-retry.version>
<spring-security.version>5.3.3.RELEASE</spring-security.version>
<spring-session-bom.version>Dragonfruit-RELEASE</spring-session-bom.version>
<spring-ws.version>3.0.9.RELEASE</spring-ws.version>
<sqlite-jdbc.version>3.31.1</sqlite-jdbc.version>
<sun-mail.version>1.6.5</sun-mail.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version>
<thymeleaf-extras-java8time.version>3.0.4.RELEASE</thymeleaf-extras-java8time.version>
<thymeleaf-extras-springsecurity.version>3.0.4.RELEASE</thymeleaf-extras-springsecurity.version>
<thymeleaf-layout-dialect.version>2.4.1</thymeleaf-layout-dialect.version>
<tomcat.version>9.0.36</tomcat.version>
<unboundid-ldapsdk.version>4.0.14</unboundid-ldapsdk.version>
<undertow.version>2.1.3.Final</undertow.version>
<versions-maven-plugin.version>2.7</versions-maven-plugin.version>
<webjars-hal-browser.version>3325375</webjars-hal-browser.version>
<webjars-locator-core.version>0.45</webjars-locator-core.version>
<wsdl4j.version>1.6.3</wsdl4j.version>
<xml-maven-plugin.version>1.0.2</xml-maven-plugin.version>
<xmlunit2.version>2.7.0</xmlunit2.version>
</properties>
從spring-boot-dependencies
的源文件可以看出,該文件通過標籤對一些常用的技術框架的依賴文件進行了統一的版本號管理,例如activemq、spring、tomcat
等,都有與Spring Boot 2.3.1
版本相匹配的版本,這也是pom.xml
引入依賴文件不需要標註依賴文件版本號的原因。
需要注意的是,如果pom.xml引入的依賴文件不是spring-boot-starter-parent
管理的,那麼在pom.xml
引入依賴文件時,需要使用標籤指定依賴文件的版本號。
問題2:spring-boot-starter-parent
父依賴啓動器的主要作用是進行統一的版本管理,那麼項目運行依賴的JAR
包從何而來?
2 spring-boot-starter-web
查看spring-boot-starter-web
的源文件:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.1.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.1.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.1.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
Spring Boot
除了提供有上述介紹的Web
依賴啓動器外,還提供了其他許多開發場景的相關依賴。我們可以打開Spring Boot
的官方文檔,搜索starters
關鍵字查詢場景依賴啓動器。
Spring Boot
並不是提供了所有場景的開發的技術框架都提供了場景啓動器,例如MyBatis、Druid
等。爲了利用Spring Boot
框架的優勢,在Spring Boot
官方沒有整合這些技術框架的情況下,MyBatis、Druid
等技術框架所在的開發團隊主動與Spring Boot
框架進行整合,實現了各自的依賴啓動器。
二、自動配置(啓動流程)
概念:能夠在我們添加jar包依賴的時候,自動爲我們配置一些組件的相關配置,我們無需配置或者只需要少量配置就能運行編寫的項目。
問題:Spring Boot
到底是如何進行自動配置的,都把哪些組件進行了自動配置?
Spring Boot
應用的啓動入口是@SpringBootApplication
註解標註類中的main()
方法,@SpringBootApplication
能夠掃描Spring
組件並自動配置Spring Boot
。
下面,查看@SpringBootApplication
內部源碼進行分析,核心代碼具體圖如下:
@SpringBootApplication
public class SpringBootExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootExampleApplication.class, args);
}
}
@Target(ElementType.TYPE) // 註解的適用範圍,Type表示註解可以描述在類、接口、註解或枚舉中
@Retention(RetentionPolicy.RUNTIME) // 表示註解的生命週期,Runtime運行時
@Documented // 表示註解可以記錄在javadoc中
@Inherited // 表示可以被子類繼承該註解
@SpringBootConfiguration // 標明該類爲配置類
@EnableAutoConfiguration // 啓動自動配置功能
@ComponentScan(excludeFilters = { // 包掃描器 <context:component-scan base-package="com.xxx.xxx"/>
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
從上面源碼可以看出,@SpringBootApplication
是一個組合註解,前面4個是註解的元數據信息,我們主要看後面3個註解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
三個核心註解,關於這三個核心註解說明具體如下:
1、@SpringBootConfiguration
@SpringBootConfiguration
註解表示Spring Boot
配置類。查看@SpringBootConfiguration
註解源碼如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //配置類
public @interface SpringBootConfiguration {
}
可以看出,@SpringBootConfiguration
註解內部有一個核心註解@Configuration
,該註解是由Spring框架提供的,表示當前類爲一個配置類(XML配置文件的註解形式),並可以被組件掃描器掃描。由此可見,@SpringBootConfiguration
註解的作用與@Configuration
註解相同,都是標識一個可以被組件掃描器掃描的配置類,只不過@SpringBootConfiguration
是被Spring Boot
進行了重新封裝命名而已。
2、@EnableAutoConfiguration
@EnableAutoConfiguration
註解表示開啓自動配置功能,該註解是Spring Boot
框架最重要的註解,也是實現自動化配置的註解。我們同樣來查看該註解的源代碼:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自動配置包 : 會把@springbootApplication註解標註的類所在包名拿到,並且對該包及其子包進行掃描,將組件添加到容器中
@Import(AutoConfigurationImportSelector.class) // 可以幫助springboot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IoC容器(ApplicationContext)中
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
下面,對這兩個核心註解分別講解:
2.1 @AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// spring框架的底層註解,它的作用就是給容器中導入某個組件類,
// 例如@Import(AutoConfigurationPackages.Registrar.class),它就是將Registrar這個組件類導入到容器中
@Import(AutoConfigurationPackages.Registrar.class) // 默認將主配置類(@SpringBootApplication)所在的包及其子包裏面的所有組件掃描到Spring容器中
public @interface AutoConfigurationPackage {
}
@Import(AutoConfigurationPackages.Registrar.class)
,它就是將Registrar
這個組件類導入到容器中,可查看Registrar
類中registerBeanDefinitions
方法,這個方法就是導入組件類的具體實現。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
// 獲取的是項目主程序啓動類所在的目錄
// metadata:註解標註的元數據信息
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 默認將會掃描@SpringBootApplication標註的主配置類所在的包及其子包下所有組件
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
使用debug跟蹤可以發現new PackageImport(metadata).getPackageName()
就是com.riemann
。
也就是說@AutoConfigurationPackage
註解的主要的作用就是將主程序類所在包及所有子包下的組件掃描到Spring
容器中。
因此,在定義項目包結構時,要求定義的包結構非常規範,項目主程序啓動類要定義在最外層的根目錄位置,然後在根目錄位置內部建立子包和類運行業務開發,這樣才能保證定義的類能夠被組件掃描器掃描。
2.2 @Import(AutoConfigurationImportSelector.class)
將AutoConfigurationImportSelector
這個類導入到Spring
容器中,AutoConfigurationImportSelector
可以幫助Spring Boot
應用將所有符合條件的@Configuration
配置都加載到當前Spring Boot
創建並使用的IoC
容器(ApplicationContext
)中,通過源碼分析這個類是通過selectImports
這個方法告訴Spring Boot
都需要導入哪些組件:
// 這個方法告訴springboot都需要導入那些組件
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判斷 enableautoconfiguration註解有沒有開啓,默認開啓(是否進行自動裝配)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 1. 加載配置文件META-INF/spring-autoconfigure-metadata.properties,從中獲取所有支持自動配置類的條件
// 作用:SpringBoot使用一個Annotation的處理器來收集一些自動裝配的條件,那麼這些條件可以在META-INF/spring-autoconfigure-metadata.properties進行配置。
// SpringBoot會將收集好的@Configuration進行一次過濾進而剔除不滿足條件的配置類
// 自動配置的類全名.條件=值
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
深入研究loadMetadata
方法:
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; // 文件中爲需要加載的配置類的類路徑
private AutoConfigurationMetadataLoader() {
}
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
// 重載方法
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// 1.讀取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚舉對象
// 獲得 PATH 對應的 URL 們
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
// 遍歷 URL 數組,讀取到 properties 中
Properties properties = new Properties();
// 2.解析urls枚舉對象中的信息封裝成properties對象並加載
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
// 將 properties 轉換成 PropertiesAutoConfigurationMetadata 對象
// 根據封裝好的properties對象生成AutoConfigurationMetadata對象返回
return loadMetadata(properties);
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
...
}
深入研究getAutoConfigurationEntry
方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// 1. 判斷是否開啓註解。如未開啓,返回空串
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 2. 獲得註解的屬性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3. getCandidateConfigurations()用來獲取默認支持的自動配置類名列表
// spring Boot在啓動的時候,使用內部工具類SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
// 找出其中key爲org.springframework.boot.autoconfigure.EnableAutoConfiguration的屬性定義的工廠類名稱,
// 將這些值作爲自動配置類導入到容器中,自動配置類就生效了
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 3.1 //去除重複的配置類,若我們自己寫的starter 可能存在重複的
configurations = removeDuplicates(configurations);
// 4. 如果項目中某些自動配置類,我們不希望其自動配置,我們可以通過EnableAutoConfiguration的exclude或excludeName屬性進行配置,
// 或者也可以在配置文件裏通過配置項“spring.autoconfigure.exclude”進行配置。
//找到不希望自動配置的配置類(根據EnableAutoConfiguration註解的一個exclusions屬性)
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 4.1 校驗排除類(exclusions指定的類必須是自動配置類,否則拋出異常)
checkExcludedClasses(configurations, exclusions);
// 4.2 從 configurations 中,移除所有不希望自動配置的配置類
configurations.removeAll(exclusions);
// 5. 對所有候選的自動配置類進行篩選,根據項目pom.xml文件中加入的依賴文件篩選出最終符合當前項目運行環境對應的自動配置類
//@ConditionalOnClass : 某個class位於類路徑上,纔會實例化這個Bean。
//@ConditionalOnMissingClass : classpath中不存在該類時起效
//@ConditionalOnBean : DI容器中存在該類型Bean時起效
//@ConditionalOnMissingBean : DI容器中不存在該類型Bean時起效
//@ConditionalOnSingleCandidate : DI容器中該類型Bean只有一個或@Primary的只有一個時起效
//@ConditionalOnExpression : SpEL表達式結果爲true時
//@ConditionalOnProperty : 參數設置或者值一致時起效
//@ConditionalOnResource : 指定的文件存在時起效
//@ConditionalOnJndi : 指定的JNDI存在時起效
//@ConditionalOnJava : 指定的Java版本存在時起效
//@ConditionalOnWebApplication : Web應用環境下起效
//@ConditionalOnNotWebApplication : 非Web應用環境下起效
// 總結一下判斷是否要加載某個類的兩種方式:
// 根據spring-autoconfigure-metadata.properties進行判斷。
// 要判斷@Conditional是否滿足
// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在類路徑中存在SqlSessionFactory.class、SqlSessionFactoryBean.class這兩個類才能完成自動註冊。
configurations = filter(configurations, autoConfigurationMetadata);
// 6. 將自動配置導入事件通知監聽器
// 當AutoConfigurationImportSelector過濾完成後會自動加載類路徑下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的實現類,
// 並觸發fireAutoConfigurationImportEvents事件。
fireAutoConfigurationImportEvents(configurations, exclusions);
// 7. 創建 AutoConfigurationEntry 對象
return new AutoConfigurationEntry(configurations, exclusions);
}
getAutoConfigurationEntry
裏有一個重要的方法getCandidateConfigurations
,這個方法是讓SpringFactoriesLoader
去加載一些組件的名字。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 讓SpringFactoryLoader去加載一些組件的名字
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
該方法主要市區加載一個外部文件,該文件在如下圖:
@EnableAutoConfiguration
就是從classpath
中搜尋META-INF/spring.factories
配置文件,並將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration
對應的配置項通過反射(Java Reflection
)實例化爲對應的標註了@Configuration的JavaConfig
形式的配置類,並加載到IoC
容器中。
直到看到了spring.factories
配置文件對應的那些類才明白了Spring Boot
爲啥不需要配置各種XML
文件了。那是因爲這些配置類的本質是傳統Spring MVC
框架中對應的XML
配置文件,只不過在Spring Boot
中以自動配置類的形式進行了預先配置。
因此,在Spring Boot項目中加入相關依賴啓動器後,基本上不需要任何配置就可以運行程序,當然,我們也可以對這些自動配置類中默認的配置進行更改。
2.3 小結
Spring Boot
底層實現自動配置的步驟是:
Spring Boot
應用啓動;@SpringBootApplication
起作用;@SpringBootConfiguration
,標明該類爲配置類;@EnableAutoConfiguration
,啓動自動配置功能;該註解有兩個子註解(@AutoConfigurationPackage
、@Import(AutoConfigurationImportSelector.class)
);@AutoConfigurationPackage
這個註解主要是@Import(AutoConfigurationPackages.Registrar.class)
,它就是將Registrar
這個組件類導入到容器中,而Registrar
類作用是掃描主配置類同級目錄及其子包裏面的所有組件掃描到Spring
容器中;@Import(AutoConfigurationImportSelector.class)
:它通過將AutoConfigurationImportSelector
類導入容器中,該類的作用是通過selectImports
方法執行過程中,會使用內部工具類SpringFactoriesLoader
,查找classpath
上所有jar
包中的META-INF/spring.factories
進行加載,實現將配置類信息交給SpringFactory
加載器進行一系列的容器創建過程。
3、@ComponentScan
@ComponentScan
註解具體掃描的包的根路徑由SpringBoot項目主程序啓動類所在包位置決定,在掃描過程中由前面介紹的@AutoConfigurationPackage
註解進行解析,從而得到Spring Boot
項目主程序啓動類所在包的具體位置。
總結:
@SpringBootApplication
的註解功能就分析的差不多了,簡單來說就是三個註解的組合註解:
|- @SpringBootConfiguration
|- @Configuration // 通過javaConfig的方式來添加組件到IOC容器中
|- @EnableAutoConfiguration
|- @AutoConfigurationPackage // 自動配置包,與@ComponentScan掃描到的添加到IOC
|- @Import(AutoConfigurationImportSelector.class) // 到META-INF/spring.factories中定義的bean添加到IOC容器中
|- @ComponentScan // 包掃描
三、執行原理
每個Spring Boot
項目都有一個主程序啓動類,在主程序啓動類中有一個啓動項目的main()
方法,在該方法中通過執行SpringApplication.run()
即可啓動整個Spring Boot
程序。
問題:那麼SpringApplication.run()
方法到底是如何做到啓動Spring Boot
項目的呢?
下面來看下run()
方法的源碼:
@SpringBootApplication
public class SpringBootExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootExampleApplication.class, args);
}
}
// 調用靜態類,參數對應的就是SpringBootExampleApplication.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// SpringApplication的啓動由兩部分組成:
// 1. 實例化SpringApplication對象
// 2. run(args):調用run方法
return new SpringApplication(primarySources).run(args);
}
1、從上述源碼可以看出,SpringApplication.run()
內部執行了兩個操作,分別是SpringApplication
實例的初始化和調用run()
啓動項目,這兩個階段的源碼分析具體如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 項目啓動類 SpringBootExampleApplication.class設置爲屬性存儲起來
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 設置應用類型是SERVLET應用(Spring 5之前的傳統MVC應用)還是REACTIVE應用(Spring 5開始出現的WebFlux交互式應用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 設置初始化器(Initializer),最後會調用這些初始化器
// 所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,在Spring上下文被刷新之前進行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 設置監聽器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 初始化 mainApplicationClass 屬性:用於推斷並設置項目main()方法啓動的主程序啓動類
this.mainApplicationClass = deduceMainApplicationClass();
}
從上述源碼可以看出,SpringApplication的初始化過程主要包括以下四部分:
1.1、this.webApplicationType = WebApplicationType.deduceFromClasspath();
public enum WebApplicationType {
NONE,
SERVLET,
REACTIVE;
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
/**
* @return 從 classpath 上,判斷 Web 應用類型。
*/
static WebApplicationType deduceFromClasspath() {
// WebApplicationType.REACTIVE 類型 通過類加載器判斷REACTIVE相關的Class是否存在
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) // 判斷REACTIVE相關的Class是否存在
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// WebApplicationType.NONE 類型
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) { // 不存在 Servlet 的類
return WebApplicationType.NONE;
}
}
// WebApplicationType.SERVLET 類型。可以這樣的判斷的原因是,引入 Spring MVC 時,是內嵌的 Web 應用,會引入 Servlet 類。
return WebApplicationType.SERVLET;
}
static WebApplicationType deduceFromApplicationContext(
Class<?> applicationContextClass) {
if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
return WebApplicationType.SERVLET;
}
if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
return WebApplicationType.REACTIVE;
}
return WebApplicationType.NONE;
}
private static boolean isAssignable(String target, Class<?> type) {
try {
return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
}
catch (Throwable ex) {
return false;
}
}
}
1.2、setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
/**
* 獲得指定類類對應的對象們。
*
* @param type 指定類
* @param <T> 泛型
* @return 對象們
* 這裏的入參type是:org.springframework.context.ApplicationContextInitializer.class
* ApplicationListener
*/
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` 裏的類名的數組
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
// org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
// 根據names來進行實例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 對實例進行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
/**
* 創建對象的數組
*
* @param type 父類
* @param parameterTypes 構造方法的參數類型
* @param classLoader 類加載器
* @param args 參數
* @param names 類名的數組
* @param <T> 泛型
* @return 對象的數組
*/
@SuppressWarnings("unchecked")
// parameterTypes: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size()); // 數組大小,細節~
// 遍歷 names 數組
for (String name : names) {
try {
// 獲得 name 對應的類
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
// 確認被加載類是ApplicationContextInitializer的子類
Assert.isAssignable(type, instanceClass);
// 獲得構造方法
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
// 創建對象
T instance = (T) BeanUtils.instantiateClass(constructor, args);
// 加入實例列表中
instances.add(instance);
} catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
/**
* ApplicationContextInitializer 數組
*/
private List<ApplicationContextInitializer<?>> initializers;
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
1.3、setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
getSpringFactoriesInstances(ApplicationListener.class)
與第2步類似。
/**
* ApplicationListener 數組
*/
private List<ApplicationListener<?>> listeners;
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
}
1.4、this.mainApplicationClass = deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
try {
// 獲得當前 StackTraceElement 數組
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 判斷哪個執行了 main 方法
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
2、項目的初始化啓動
分析完(new SpringApplication(primarySources)).run(args)
源碼的前一部分SpringApplication
實例對象的初始化創建後,查看run(args)
方法執行的項目初始化啓動過程,核心代碼具體如下:
public ConfigurableApplicationContext run(String... args) {
// 創建 StopWatch 對象,並啓動。StopWatch 主要用於簡單統計 run 啓動過程的時長。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 初始化應用上下文和異常報告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 headless 屬性
configureHeadlessProperty();
//(1)獲取並啓動監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 創建 ApplicationArguments 對象 初始化默認應用參數類
// args是啓動Spring應用的命令行參數,該參數可以在Spring應用中被訪問。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//(2)項目運行環境Environment的預配置
// 創建並配置當前SpringBoot應用將要使用的Environment
// 並遍歷調用所有的SpringApplicationRunListener的environmentPrepared()方法
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 準備Banner打印器 - 就是啓動Spring Boot的時候打印在console上的ASCII藝術字體
Banner printedBanner = printBanner(environment);
//(3)創建Spring容器
context = createApplicationContext();
// 獲得異常報告器 SpringBootExceptionReporter 數組
//這一步的邏輯和實例化初始化器和監聽器的一樣,
// 都是通過調用 getSpringFactoriesInstances 方法來獲取配置的異常類名稱並實例化所有的異常處理類。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//(4)Spring容器前置處理
//這一步主要是在容器刷新之前的準備動作。包含一個非常關鍵的操作:將啓動類注入容器,爲後續開啓自動化配置奠定基礎。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//(5):刷新容器
refreshContext(context);
//(6):Spring容器後置處理
// 擴展接口,設計模式中的模板方法,默認爲空實現。
// 如果有自定義需求,可以重寫該方法。比如打印一些啓動結束log,或者一些其它後置處理
afterRefresh(context, applicationArguments);
// 停止 StopWatch 統計時長
stopWatch.stop();
// 打印 Spring Boot 啓動的時長日誌。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//(7)發出結束執行的事件通知
listeners.started(context);
//(8):執行Runners
// 用於調用項目中自定義的執行器XxxRunner類,使得在項目啓動完成後立即執行一些特定程序
// Runner 運行器用於在服務啓動時進行一些業務初始化操作,這些操作只在服務啓動後執行一次。
// Spring Boot提供了ApplicationRunner和CommandLineRunner兩種服務接口
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 如果發生異常,則進行處理,並拋出 IllegalStateException 異常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
// (9)發佈應用上下文就緒事件
// 表示在前面一切初始化啓動都沒有問題的情況下,使用運行監聽器SpringApplicationRunListener持續運行配置好的應用上下文ApplicationContext,
// 這樣整個Spring Boot項目就正式啓動完成了。
try {
listeners.running(context);
} catch (Throwable ex) {
// 如果發生異常,則進行處理,並拋出 IllegalStateException 異常
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
// 返回容器
return context;
}
2.1 獲取並啓動監聽器 SpringApplicationRunListeners listeners = getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
// 這裏仍然利用了getSpringFactoriesInstances方法來獲取實例,
// 從META-INF/spring.factories中讀取Key爲org.springframework.boot.SpringApplicationRunListener的Values
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
2.2 項目運行環境Environment的預配置 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
/**
* 加載外部化配置資源到environment,包括命令行參數、servletConfigInitParams、
* servletContextInitParams、systemProperties、sytemEnvironment、random、
* application.yml(.yaml/.xml/.properties)等;初始化日誌系統。
*/
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// 獲取或創建環境(存在就直接返回,不存在創建一個再返回)
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置環境:配置PropertySources和active Profiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// listeners環境準備(就是廣播ApplicationEnvironmentPreparedEvent事件)。
listeners.environmentPrepared(environment);
// 將環境綁定到SpringApplication
bindToSpringApplication(environment);
// 如果非web環境,將環境轉換成StandardEnvironment
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 配置PropertySources對它自己的遞歸依賴
// 如果有 attach 到 environment 上的 MutablePropertySources,則添加到 environment 的 PropertySource 中。
ConfigurationPropertySources.attach(environment);
return environment;
}
2.3 創建Spring容器 context = createApplicationContext();
protected ConfigurableApplicationContext createApplicationContext() {
// 根據 webApplicationType 類型,獲得 ApplicationContext 類型
// 這裏創建容器的類型 還是根據webApplicationType進行判斷的,
// 該類型爲SERVLET類型,所以會通過反射裝載對應的字節碼,
// 也就是AnnotationConfigServletWebServerApplicationContext
// 先判斷有沒有指定的實現類
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);
}
}
// 創建 ApplicationContext 對象
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
2.4 Spring容器前置處理 prepareContext(context, environment, listeners, applicationArguments,printedBanner);
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 設置容器環境,包括各種變量
context.setEnvironment(environment);
// 設置上下文的 bean 生成器和資源加載器
postProcessApplicationContext(context);
// 執行容器中的ApplicationContextInitializer(包括 spring.factories和自定義的實例)
applyInitializers(context);
// 觸發所有 SpringApplicationRunListener 監聽器的 contextPrepared 事件方法
listeners.contextPrepared(context);
// 記錄啓動日誌
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 註冊啓動參數bean,這裏將容器指定的參數封裝成bean,注入容器
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
// 加載所有資源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加載我們的啓動類,將啓動類注入容器,爲後續開啓自動化配置奠定基礎
load(context, sources.toArray(new Object[0]));
// 觸發所有 SpringApplicationRunListener 監聽器的 contextLoaded 事件方法
listeners.contextLoaded(context);
// 這塊會對整個上下文進行一個預處理,比如觸發監聽器的響應事件、加載資源、設置上下文環境等等
}
2.5 刷新容器 refreshContext(context);
private void refreshContext(ConfigurableApplicationContext context) {
// 開啓(刷新)Spring 容器,通過refresh方法對整個IoC容器的初始化(包括Bean資源的定位、解析、註冊等等)
refresh(context);
// 註冊 ShutdownHook 鉤子
if (this.registerShutdownHook) {
try {
// 向JVM運行時註冊一個關機鉤子,在JVM關機時關閉這個上下文,除非它當時已經關閉
context.registerShutdownHook();
} catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
@Override
public void refresh() throws BeansException, IllegalStateException {
/**
* 對象鎖加鎖
*/
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
/**
* 第一步:刷新前的預處理
*
* 表示在真正做refresh操作之前需要準備做的事情:
* 設置Spring容器啓動時間
* 開啓活躍狀態,撤銷關閉狀態
* 驗證環境信息裏一些必須存在的屬性
*/
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
/**
* 第二步:
*
* 獲取BeanFactory:默認實現是DefaultListableBeanFactory
* Bean獲取並封裝成BeanDefinition對象
* 加載BeanDefinition 並註冊到BeanDefinitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
/**
* 第三步:
*
* 獲取BeanFactory的準備工作:BeanFactory進行一些設置,比如context的類加載器等。
*/
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
/**
* 第四步:
*
* BeanFactory準備工作完成後進行的後置處理工作
*/
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
/**
* 第五步:
*
* 實例化實現了BeanFactoryPostProcessor接口的Bean,並調用接口方法
*/
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
/**
* 第六步:
*
* 註冊BeanPostProcessor(Bean的後置處理器),在創建bean的前後等執行
*/
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
/**
* 第七步:
*
* 初始化MessageSource組件(做國際化功能、消息綁定、消息解析)
*/
initMessageSource();
// Initialize event multicaster for this context.
/**
* 第八步:
*
* 初始化事件派發器
*/
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
/**
* 第九步:
*
* 子類重寫這個方法,在容器刷新的時候可以自定義邏輯,如創建Tomcat、Jetty等WEB服務器
*/
onRefresh();
// Check for listener beans and register them.
/**
* 第十步:
*
* 註冊應用的監聽器,就是註冊實現了ApplicationListener接口的監聽器bean
*/
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
/**
* 第十一步:
*
* 初始化所有剩下的非懶加載的單例bean
* 初始化創建非懶加載方式的單例Bean實例(未設置屬性)
* 填充屬性
* 初始化方法調用(比如調用afterPropertiesSet方法、init-method方法)
* 調用BeanPostProcessor(Bean的後置處理器)對實例bean進行後置處理
*/
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
/**
* 第十二步:
*
* 完成context的刷新,主要是調用LifecycleProcessor的onRefresh()方法,並且發佈事件(ContextRefreshEvent)
*/
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
2.6 Spring容器後置處理 afterRefresh(context, applicationArguments);
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
// 該方法沒有實現,可以根據需要做一些定製化的操作。
}
2.7 發出結束執行的事件通知 listeners.started(context);
public void started(ConfigurableApplicationContext context) {
// 執行所有SpringApplicationRunListener實現的started方法。
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
2.8 執行Runners callRunners(context, applicationArguments);
private void callRunners(ApplicationContext context, ApplicationArguments args) {
// 獲得所有 Runner 們
List<Object> runners = new ArrayList<>();
// 獲得所有 ApplicationRunner 實現類
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 獲得所有 CommandLineRunner 實現類
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 排序 runners
AnnotationAwareOrderComparator.sort(runners);
// 遍歷 Runner 數組,執行邏輯
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
2.9 發佈應用上下文就緒事件 listeners.running(context);
public void running(ConfigurableApplicationContext context) {
// 觸發所有 SpringApplicationRunListener 監聽器的 running 事件方法。
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
四、Spring Boot 執行流程圖
下面來畫一張Spring Boot
執行流程圖,讓大家更清晰的知道Spring Boot
整體執行流程與主要啓動階段:
好了,至此,SpringBoot
原理深入以及源碼分析要和小夥伴們告一段落了,我們下一篇再見。