SpringBoot啟動過程逐步分析講解
springboot啟動是通過一個main方法啟動的,代碼如下
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
從該方法我們一路跟進(jìn)去,進(jìn)入SpringApplication的構(gòu)造函數(shù),我們可以看到如下代碼primarySources,為我們從run方法塞進(jìn)來的主類,而resourceLoader此處為null,是通過構(gòu)造函數(shù)重載進(jìn)來,意味著這里還有其它方式的用法,先繞過。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//此處推斷web容器類型是servlet 還是REACTIVE還是啥也不是即當(dāng)前非web容器
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//此處進(jìn)入springFacories的加載 即SpringFactoriesLoader,不過此處是過濾處所有ApplicationContextInitializer的子類
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//從springFactories的緩存中再過濾處 ApplicationListener 的子類
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
所以我們來看SpringFactoriesLoader中的loadSpringFactories方法,先獲取資源路徑META-INF/spring.factories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//classLoader 上一步傳進(jìn)來的為AppClassLoader,如果對classLoader不了解需要去看看java的類加載機(jī)制,以及雙親委托機(jī)制,此處的資源加載也是個雙親委托機(jī)制。
//此處如果appclassLoader不為null,那么遍歷遍歷這個classLoader所加載的包,中是否存在META-INF/spring.factories這個文件
//如果存在最終生成一個集合,然后遍歷集合
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//從集合中取出一個spring.factories文件
UrlResource resource = new UrlResource(url);
//讀取對應(yīng)文件內(nèi)容,作為一個properties對象
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
//解析文件內(nèi)容,并將之放入一個LinkedMultiValueMap中,key就是spring.factories中等號前面的部分,值是后面部分根據(jù)都好組成的list。并放入緩存?zhèn)溆?,緩存的key為對應(yīng)的classLoader
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
以上代碼執(zhí)行完成后形成的內(nèi)容大概是這樣的,key表示factoryClassName
result = new LinkedMultiValueMap<>();
list=new LinkList();
list.add("com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration");
list.add("org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration")
result.put("org.springframework.boot.autoconfigure.EnableAutoConfiguration",list);
所以此時回退到這個方法setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
就是從遍歷出來的所有的這些內(nèi)容中獲取出key為org.springframework.context.ApplicationContextInitializer 的集合
設(shè)置該當(dāng)前類的initializers集合,后面的Listeners 集合也是一樣的過程。只不過,后一次獲取是從上一次的緩存中取的。
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
設(shè)置完initializer和listener集合后,進(jìn)行主函數(shù)推斷
接著看代碼,此處對主函數(shù)的推斷非常的巧妙,就是利用虛擬機(jī)運(yùn)行時已被入棧的所有鏈路一路追蹤到方法名為main的函數(shù),然后獲取到他的類名。
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
此時已經(jīng)將SpringApplication 對象new出來了,然后接著執(zhí)行它的run方法,先總結(jié)下以上代碼干了幾個事情
- this.primarySources 設(shè)置了主類集合
- this.webApplicationType 設(shè)置了web容器類型
- setInitializers 設(shè)置了ApplicationContextInitializer
- setListeners 設(shè)置了ApplicationListener
- mainApplicationClass 設(shè)置了入口類
- 加載并且解析了所有的spring.factories文件并緩存起來,注意此時只是解析成屬性。
我們發(fā)現(xiàn)一個奇怪的現(xiàn)象既然已經(jīng)有了this.primarySources 為什么還要來個mainApplicationClass呢?mainApplicationClass目前看來除了日志打印以及banner之外沒有什么其它作用。
接著看run方法
public ConfigurableApplicationContext run(String... args) {
//計時器,不管它
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//告訴程序當(dāng)前為無頭模式,沒有外設(shè),如果需要調(diào)用外設(shè)接口需要你自己模擬,這些內(nèi)容再awt包中做了,所以你只要告訴它有沒有外設(shè)就可以了
configureHeadlessProperty();
//繼續(xù)從spring.factories中獲取對應(yīng)的對象,此時listeners中有一個默認(rèn)實(shí)現(xiàn)EventPublishingRunListener,如有需要這里是可進(jìn)行擴(kuò)展
SpringApplicationRunListeners listeners = getRunListeners(args);
//遍歷所有的listeners并調(diào)用它的starting方法,廣播啟動過程中的事件,注意這里是個廣播器不是監(jiān)聽器,名字有點(diǎn)不好理解,實(shí)現(xiàn)了
//ApplicationListener的接口就接收到對應(yīng)事件之后執(zhí)行對應(yīng)操作,而我們前面也有設(shè)置了個listeners集合,為ApplicationListener
//此處名字起的讓人容易誤解,明明是個廣播器非要叫RunListener
listeners.starting();
try {
//進(jìn)行控制臺參數(shù)解析,具體如何解析,此處不展開,后續(xù)對配置文件解析的文章在詳細(xì)介紹
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//此處配置個內(nèi)省beanInfo時是否緩存的開關(guān),true就緩存,false,就不緩存
configureIgnoreBeanInfo(environment);
//打印banner,這個不介紹了比較簡單的東西
Banner printedBanner = printBanner(environment);
//此處開始創(chuàng)建容器,根據(jù)給定的容器類型也就是上面獲取到的webApplicationType創(chuàng)建對應(yīng)的容器
//servlet :org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
//reactive:org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
//default: org.springframework.context.annotation.AnnotationConfigApplicationContext 非web容器
context = createApplicationContext();
//異常解析器,如果出現(xiàn)異常了,會在被catch起來,然后通過解析器鏈進(jìn)行解析。這個也是從spring.factories中獲取的
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//上面創(chuàng)建了容器,這邊對容器做一些初始化操作
//1、設(shè)置環(huán)境變量env
//2、執(zhí)行postProcessApplicationContext 準(zhǔn)備beanNameGenerator,resourceLoader,ApplicationConversionService
//3、調(diào)用前面獲取到的ApplicationContextInitializer
//4、廣播容器準(zhǔn)備完成事件
//5、添加了一個制定的單例就是banner打印用的
//6、在加載前,設(shè)置是否允許bean被覆蓋屬性,默認(rèn)false
//7、開始加載,入口為main函數(shù)傳入的primarySources,先創(chuàng)建個BeanDefinitionLoader,并將第二步準(zhǔn)備的實(shí)例設(shè)置給他,
//最后由BeanDefinitionLoader.load()承當(dāng)所有的加載動作,加載過程也很長,先按下不表。
//8、廣播容器加載事件
//到此容器準(zhǔn)備完成
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//開始執(zhí)行spring的refresh方法,此處不多介紹了
refreshContext(context);
//容器refresh完成后留了個擴(kuò)展,也不知道是不是擴(kuò)展,這個方法需要去自定義啟動類,有點(diǎn)奇怪
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//然后廣播啟動完成事件
listeners.started(context);
//最后執(zhí)行所有的runners,也就是實(shí)現(xiàn)了ApplicationRunner接口的類是最后執(zhí)行,所以此時你可以做一些其它的動作,比如注冊到注冊中心,比如啟動netty等等。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
//在廣播一個運(yùn)行中的事件
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}上述代碼中在prepareContext階段完成了第一階段的beanDefinition注冊,此處僅注冊了第一bean 通過主類傳進(jìn)去的那個類。之后再refresh階段,通過解析該類進(jìn)行第二輪的beandefinition注冊。這一部分屬于spring-context的內(nèi)容了。
在refresh階段,因?yàn)橹黝愖鳛橐粋€配置類,自然也是需要進(jìn)行一輪解析的,所以接下來的步驟就是解析@SpringbootApplication,此注解為一個復(fù)合注解
@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) })
內(nèi)容解析:
- @SpringBootConfiguration 等同于@Configuration
- @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
此處導(dǎo)入了一個AutoConfigurationImportSelector ,同時還通過@AutoConfigurationPackage導(dǎo)入了一個AutoConfigurationPackages.Registrar.class
- 接下來是一個@ComponentScan注解
而我們知道在spring的配置類(單一類)的解析過程中的順序是
- 先解析Component系列的注解
- 再解析@PropertySource
- 接著解析@ComponentScan與@ComponentScans注解
- 接著解析@Import
- 接著解析ImportResource
- 最后解析@Bean注解
而再Import中又分為幾種類型
- ImportSelector
- DeferredImportSelector
- ImportBeanDefinitionRegistrar
- 普通的import類
他們的加載順序?yàn)?,普通的?ndash;>importSelector–deferredImportSelector–>ImportResource–>ImportBeanDefinitionRegistrar
綜上所述spring.factories聲明的配置類看來也不是墊底解析的,所以如果有遇到需要順序的場景可以參照這個順序來聲明即可。
到此這篇關(guān)于SpringBoot啟動過程逐步分析講解的文章就介紹到這了,更多相關(guān)SpringBoot啟動過程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ IDEA使用教程從入門到上癮(2019圖文版)
這篇文章主要介紹了IntelliJ IDEA使用教程從入門到上癮(2019圖文版),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
使用Jacoco獲取 Java 程序的代碼執(zhí)行覆蓋率的步驟詳解
這篇文章主要介紹了使用Jacoco獲取 Java 程序的代碼執(zhí)行覆蓋率的步驟詳解,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下2021-03-03
使用GSON庫將Java中的map鍵值對應(yīng)結(jié)構(gòu)對象轉(zhuǎn)換為JSON
GSON是由Google開發(fā)并開源的實(shí)現(xiàn)Java對象與JSON之間相互轉(zhuǎn)換功能的類庫,這里我們來看一下使用GSON庫將Java中的map鍵值對應(yīng)結(jié)構(gòu)對象轉(zhuǎn)換為JSON的示例:2016-06-06
舉例講解設(shè)計模式中的訪問者模式在Java編程中的運(yùn)用
這篇文章主要介紹了舉例講解設(shè)計模式中的訪問者模式在Java編程中的運(yùn)用,訪問者模式是一種將算法與對象結(jié)構(gòu)分離的軟件設(shè)計模式,需要的朋友可以參考下2016-05-05
Java并發(fā)容器ConcurrentLinkedQueue解析
這篇文章主要介紹了Java并發(fā)容器ConcurrentLinkedQueue解析,2023-12-12
SpringBoot的@RestControllerAdvice作用詳解
這篇文章主要介紹了SpringBoot的@RestControllerAdvice作用詳解,@RestContrllerAdvice是一種組合注解,由@ControllerAdvice,@ResponseBody組成,本質(zhì)上就是@Component,需要的朋友可以參考下2024-01-01
Java實(shí)現(xiàn)XML與JSON秒級轉(zhuǎn)換示例詳解
這篇文章主要為大家介紹了Java實(shí)現(xiàn)XML與JSON秒級轉(zhuǎn)換示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09

