SpringBoot啟動流程源碼解析
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | Lucky帥小武
來源 | urlify.cn/A7rErq
前言
SpringBoot本質(zhì)上沒有技術(shù)革新,而是在Spring框架的基礎(chǔ)之上簡化了系統(tǒng)的配置,核心功能是自動裝配和starter組件
一、SpringBoot核心功能
1、獨(dú)立運(yùn)行程序(SpringBoot程序可以不依賴其他容器獨(dú)立運(yùn)行)
2、內(nèi)嵌Servlet容器(內(nèi)嵌Servlet容器,不依賴其他Web容器)
3、提供starter組件簡化maven配置(只需要依賴一個(gè)starter組件就可以自動依賴相關(guān)jar包,簡化maven配置)
4、自動裝配配置(為大多數(shù)應(yīng)用場景提供了默認(rèn)配置,大幅度減少了應(yīng)用配置)
二、SpringBoot常用注解
| 注解 | 修飾 | 用途 | 案例 |
| @ComponentScan | 類 | 自動掃描,修飾某個(gè)類則Spring會自動掃描當(dāng)前類所在包路徑下所以被@Component修飾的類,也可以通過basePackages自定義掃描包的根目錄 | @ComponentScan(basePackages="com.test.lucky") |
| @Component | 類 | 聲明當(dāng)前類表示當(dāng)前類為一個(gè)組件,Spring容器會很自動掃描被@Component修飾的類加載bean到Spring容器中 | @Component(value="beanName") |
| @Controller | 類 | 本質(zhì)是一個(gè)Component,聲明當(dāng)前類表示當(dāng)前類是一個(gè)控制器類,分發(fā)處理器會掃描被@Controller注解修飾的類的方法 | @Controller(value="testController") |
| @RequestMapping | 方法 | 聲明方法表示請求URL映射的方法 | @RequestMapping(value="/test") |
| @ResponseBody | 方法/類 | 將controller的方法返回的對象通過適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后,寫入到response對象的body區(qū),通常用來返回JSON數(shù)據(jù)或者是XML數(shù)據(jù) | @ResponseBody |
| @RestController | 類 | 等同于@Controller + @ResponseBody組合,表示當(dāng)前類的所有方法都自動被@ResponseBody注解修飾 | @RestController(value="testController") |
| @Service | 類 | 本質(zhì)是一個(gè)Component, 聲明當(dāng)前類是Service層的bean | @Service(value="testService") |
| @Repository | 類 | 本質(zhì)是一個(gè)Component, 聲明當(dāng)前類是Dao層的bean | @Repository(value="testDao") |
| @Configuration | 類 | 本質(zhì)是一個(gè)Component, 表面當(dāng)前類是一個(gè)配置類,會生成一個(gè)代理對象,表示當(dāng)前類是@Bean定義的源類 | @Configuration |
| @Bean | 方法 | 相當(dāng)于<bean>標(biāo)簽,被@Bean修飾的方法返回對象會被加載到Spring容器中 | @Bean(name="beanName") |
| @Import | 類 | 用于導(dǎo)入第三方包中的bean,導(dǎo)入的bean自動加載到Spring容器中 | @Import(value=Test.class) |
| @Value | 方法/屬性 | 將常量、配置中的值、其他bean注入到變量中 | @Value("${properteis.test}") |
三、SpringBoot啟動流程解析
SpringBoot的功能是基于Spring框架,所以啟動時(shí)的核心步驟自然少不了Spring容器的初始化以及Spring容器的啟動過程,另外SpringBoot還提供了自動裝配配置功能。
1、SpringBootApplication.run()方法
SpringBoot程序啟動類案例如下:
@SpringBootApplication
public class BootStrap {
public static void main(String[] args){
ApplicationContext applicationContext = SpringApplication.run(BootStrap.class);
}
}
SpringBoot程序啟動入口,代碼比較簡潔,除了被@SpringBootApplication注解修飾之外,main豐富直接調(diào)用SpringBootApplication類的靜態(tài)方法run方法即可,最終會是調(diào)用了SpringBootApplication的實(shí)例方法run方法,核心邏輯如下:
public ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext context = null;
/** 1.通過反射創(chuàng)建ApplicationContext對象 */
context = createApplicationContext();
/** 2.準(zhǔn)備ApplicationContext上下文環(huán)境 */
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
/** 3.刷新ApplicationContext */
refreshContext(context);
/** 4.刷新ApplicationContext后置處理*/
afterRefresh(context, applicationArguments);
return context;
}
可以看出SpringBoot程序啟動的核心邏輯實(shí)際就兩步,1、創(chuàng)建IOC容器ApplicationContext實(shí)例;2、啟動IOC容器。
而SpringBoot的其他核心功能很顯然就是通過@SpringBootApplication注解來實(shí)現(xiàn),這個(gè)注解才是SpringBoot的核心實(shí)現(xiàn)
2、@SpringBootApplication注解
@SpringBootApplication注解定義如下:
@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
從定義可以看出@SpringBootApplication是一個(gè)組合注解,內(nèi)部包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三個(gè)注解
2.1、@SpringBootConfiguration
@SpringBootConfiguration注解定義如下:
1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Configuration
5 public @interface SpringBootConfiguration
可以看出@SpringBootConfiguration本質(zhì)上就是一個(gè)@Configuration注解,標(biāo)注當(dāng)前類是一個(gè)配置類
2.2、@ComponentScan
@ComponentScan注解定義如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan @ComponentScan注解表示自動掃描,會掃描當(dāng)前類包路徑下所有被@Component注解修飾的bean
2.3、@EnableAutoConfiguration
@EnableAutoConfiguration注解定義如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
可以看出@EnableAutoConfiguration注解也是一個(gè)組合注解,包含了@AutoConfigurationPackage和@Import注解,再看@AutoConfigurationPackage注解的定義:
1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @Import(AutoConfigurationPackages.Registrar.class)
6 public @interface AutoConfigurationPackage
該注解內(nèi)部包含了一個(gè)@Import注解,所以可以得出結(jié)論@EnableAutoConfiguration實(shí)際上就是由兩個(gè)@Import注解組成,分別導(dǎo)入了AutoConfiguartionImportSelector和AutoConfigurationPackages.Registrar類的實(shí)例。
所以總結(jié)可以得出這兩個(gè)類是實(shí)現(xiàn)SpringBoot自動裝載配置的核心實(shí)現(xiàn)。
關(guān)于@Import注解的詳細(xì)用法這里不多介紹,先簡單介紹下,@Import注解有三種用法,分別如下:
1.顯示引入需要加載的bean,如:@Import(value=TestService.class)
2.引入實(shí)現(xiàn)ImportSelector接口的類,如自定義MyImportSelector類實(shí)現(xiàn)ImportSelector并重寫selectImports方法,返回需要導(dǎo)入的bean的數(shù)組,案例如下:
1 public class MyImportSelector implements ImportSelector {
2 @Override
3 public String[] selectImports(AnnotationMetadata importingClassMetadata) {
4 return new String[]{UserService.class.getName(), GoodsService.class.getName()};
5 }
6 }
3.引入實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口的類,如自定義MyImportBeanDefinitionRegistrar并重寫registBeanDefinitions方法,手動注冊需要加載的bean,案例如下:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
/** 手動注冊需要加載的bean*/
registerBeanDefinitions(importingClassMetadata, registry);
}
}
回過頭再看@EnableAutoConfiguration注解,分別導(dǎo)入了ImportSelector接口的實(shí)現(xiàn)類AutoConfigurationImportSelector和ImportBeanDefinitionRegistrar的實(shí)現(xiàn)類AutoConfigurationPackages.Registrar,接下來依次分析。
2.4、AutoConfigurationImportSelector
AutoConfigurationImportSelector實(shí)現(xiàn)了ImportSelector接口,所以會加載selectImports方法返回的String數(shù)組中的beanName,所以重點(diǎn)是selectImports方法,源碼如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
/** 加載元數(shù)據(jù)*/
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
核心步驟就兩步,第一步是執(zhí)行l(wèi)oadMetadata方法,源碼如下:
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
/** 加載指定路徑下的元數(shù)據(jù)*/
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
/** 遍歷Spring-autoconfigure-metadata.properties配置文件,讀取數(shù)據(jù)封裝為AutoConfigurationMetadata對象*/
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
邏輯是從加載路徑下找到spring-autoconfigure-metadata.properties配置文件,然后遍歷讀取配置文件內(nèi)容加入到Properties對象中,并封裝成AutoConfigurationMetadata對象
spring-autoconfigure-metadata.properties配置文件位于spring-boot-autoconfigure包中的META-INF目錄下,暫時(shí)可以不關(guān)心作用是什么,目前可以得出的結(jié)論是會先把這個(gè)配置中的內(nèi)容全部加載出來。
再看第二步getAutoConfigurationEntry方法的實(shí)現(xiàn),源碼如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
/** 封裝加載的配置元數(shù)據(jù)為AnnotationAttributes對象*/
AnnotationAttributes attributes = getAttributes(annotationMetadata);
/** 獲取所有候選的配置信息集合 (IOC容器需要加載的bean)
* 實(shí)際是加載META-INF/spring.factories配置文件中的bean
* */
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
/** 去除重復(fù)的*/
configurations = removeDuplicates(configurations);
/** 去除排除的*/
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
/** 去除過濾的*/
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
/** 封裝成AutoConfigurationEntry對象*/
return new AutoConfigurationEntry(configurations, exclusions);
}
目的是加載META-INF/spring.factories配置文件,得到所有需要加載的類,然后通過去除、排除的方式篩選需要加載的類
而spring.factories中有一個(gè)key為EnableAutoConfiguration,value是一個(gè)集合,包含了幾乎常用的所有各種中間件的自動配置類,這些類都在spring-boot-autoconfigure包中,包括常用的有:
MongoDataAutoConfiguration、RedisAutoConfiguration、DataSourceAutoConfiguration、RabbitAutoConfiguration、ElasticsearchAutoConfiguration等等,每個(gè)類都封裝了各自的自動配置類
tips:那么問題來了,SpringBoot默認(rèn)提供了各種各有的配置類,如果我們的程序中并沒有用到相關(guān)的組件,會不會講這些無用的配置類也加載到容器中呢?
答案很顯然是否定的,spring-autoconfigure-metadata.properties中定義了各種組件的ConditionalOnClass的key,值為對應(yīng)的Class全路徑,比如:
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate
ConditionalOnClass代表如果要加載對應(yīng)的屬性,必要要加載對應(yīng)的類,比如程序中用到了RabbitMQ,那么肯定依賴了RabbitMQ對應(yīng)的jar包,所以肯定會加載RabbitTemplate類,那么此時(shí)才會加載RabbitAutoConfiguration類,同理適用RedisAutoConfiguration類的前提是需要先加載RedisOperations類,只有先加載了對應(yīng)的類才會加載對應(yīng)的AutoConfiiguration類
總結(jié)
1、SpringBoot啟動時(shí)會通過@EnableAutoConfiguration注解導(dǎo)入AutoConfigurationImportSelector對象,會從掃描包路徑下META-INF/spring.factories中的配置,該配置文件中包含了各種組件的自動配置類對應(yīng)的AutoConfiguration類,然后通過加載各種組件的AutoConfiguration類從而可以加載對應(yīng)的自動配置類,并且通過ConditionalOnClass來過濾需要的自動配置類,去除掉程序中沒有使用的自動配置類
2、@SpringBootApplication是個(gè)組合注解,分別是@SpringBootConfiguration、@ComponentScan和@EnableAutoConfiguration三個(gè)組件,其中@SpringBootConfiguration和@ComponentScan兩個(gè)組件保證可以掃描用戶自定義的bean,@EnableAutoConfiguration用于掃描加載默認(rèn)的第三方的bean
3、SpringBoot程序啟動的流程實(shí)際就是IOC容器創(chuàng)建和啟動的過程,然后通過掃描用戶自定義的bean和第三方的bean完成bean的加載。
四、SpringBoot內(nèi)置Tomcat源碼解析
SpringBoot既然內(nèi)置了web容器,那么就不會只內(nèi)置Tomcat一個(gè)容器,還有Jetty、Undertow,所以從設(shè)計(jì)者的角度就會在這三個(gè)具體容器之上進(jìn)行抽象,定義了一個(gè)WebServer接口,WebServer接口定義如下:
public interface WebServer {
/** 啟動web容器*/
void start() throws WebServerException;
/** 關(guān)閉web容器*/
void stop() throws WebServerException;
/** 獲取web容器監(jiān)聽的端口 */
int getPort();
}
而Tomcat、Jetty、Undertow等容器就會有各自的實(shí)現(xiàn)類實(shí)現(xiàn)了WebServer接口,分別是TomcatWebServer、JettyWebServer、UndertowWebServer,而SpringBoot采用了工廠模式來決定使用哪一個(gè)容器模式,所以定義了工廠類ServertWebServerFactory。在spring.factories配置文件中就定義了ServletWebServerFactoryAutoConfiguration,該類的定義如下:
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration
可以看出該類通過@Import注解分別導(dǎo)入了EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow的bean,以EmbeddedTomcat為例,定義如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
該類內(nèi)部通過@Bean注解定義了一個(gè)TomcatServletWebServerFactory對象,該對象就是TomcatWebServer的工廠類,TomcatServletWebServerFactory有一個(gè)getWebServer方法,定義如下:
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
1 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
2 return new TomcatWebServer(tomcat, getPort() >= 0);
3 }
源碼不復(fù)雜,創(chuàng)建了一個(gè)Tomcat對象,然后初始化參數(shù),再傳給TomcatWebServer的構(gòu)造函數(shù)創(chuàng)建TomcatWebServer對象,TomcatWebServer構(gòu)造函數(shù)中會調(diào)用Tomcat的start方法啟動Tomcat服務(wù)器
tips:那么問題來了,getWebServer方法是何時(shí)調(diào)用的呢?
在Spring容器創(chuàng)建之后會執(zhí)行刷新操作,ApplicationContext的子類ServletWebServerApplicationContext實(shí)現(xiàn)了ApplicationContext的onRefresh方法,源碼如下:
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
首先是調(diào)用父類的onRefresh方法,然后執(zhí)行createWebServer方法創(chuàng)建WebServer對象,邏輯就是從IOC容器中找到ServletWebServerFactory對象,執(zhí)行g(shù)etWebServer方法獲取WebServer對象。
apache的Tomcat實(shí)現(xiàn)原理解析可參考:
五、如何實(shí)現(xiàn)自定義starter
1、新建項(xiàng)目spring-boot-mytest-starter(官方提供的starter命名為spring-boot-starter-xxx,自定義的通常命名為spring-boot-xxx-starter,用于和官方提供的區(qū)分開)
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
