SpringBoot啟動(dòng)流程入口參數(shù)創(chuàng)建對(duì)象源碼分析
入口
這不最近到金三銀四的季節(jié)了么,有個(gè)朋友去參加了一個(gè)面試,回來(lái)的時(shí)候給我說(shuō)其它還可以,但是問(wèn)到SpringBoot的啟動(dòng)原理了,說(shuō)了解的不深,我仔細(xì)轉(zhuǎn)過(guò)頭來(lái)也想了一下自己用了這么長(zhǎng)時(shí)間的SpringBoot,說(shuō)實(shí)話還真沒(méi)有仔細(xì)研究過(guò)他的啟動(dòng)原理。我覺(jué)得還是有必要去研究一下。
版本: 2.1.8.RELEASE
于是乎,我便打算從他的啟動(dòng)類上追下源碼,仔細(xì)去尋找一下答案。
啟動(dòng)代碼:
@SpringBootApplcation public static void main(String[] args) { SpringApplication.run(BlogAdminApplication.class, args); System.out.println("======== admin start success... =========="); }
這里傳入了兩個(gè)參數(shù),BlogAdminApplication當(dāng)前類和args參數(shù)
我們點(diǎn)擊進(jìn)入run方法查看
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class[]{primarySource}, args); }
這里是將我們寫的啟動(dòng)類傳入到了Class[]數(shù)組中,這步就是個(gè)單純的參數(shù)轉(zhuǎn)換。
探討primarySource參數(shù)
那么問(wèn)題是primarySource能接受的類型是啥樣的,是不是什么類都可以接受,帶著這個(gè)疑問(wèn),我們做一個(gè)測(cè)試,把這個(gè)參數(shù)給換成一個(gè)別的類呢,ManagerController類是一個(gè)我寫的接口類
@SpringBootApplication public class BlogProjectApplication { public static void main(String[] args) { SpringApplication.run(ManagerController.class, args); System.out.println("======== admin start success... =========="); } }
控制臺(tái)打印
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
提示不能啟動(dòng)服務(wù),提示缺少了ServletWebServerFactory bean 點(diǎn)進(jìn)這個(gè)類看下
@FunctionalInterface public interface ServletWebServerFactory { WebServer getWebServer(ServletContextInitializer... initializers); }
他被FunctionalInterface標(biāo)注了,是一個(gè)函數(shù)式接口,只有一個(gè)getWebServer方法,用來(lái)獲取webServer的 看下他的實(shí)現(xiàn)類,
這不就是提示我們?nèi)鄙賳?dòng)的服務(wù)容器么,說(shuō)的直白點(diǎn),我的理解就是他缺少可以運(yùn)行的容器,我們知道,沒(méi)有使用springboot項(xiàng)目之前,我們的項(xiàng)目都是跑在tomcat容器上的,當(dāng)然也有使用Jetty容器的。再者,我們知道SpringBoot是對(duì)tomcat進(jìn)行了內(nèi)置。而SpringBoot不僅僅是只有內(nèi)置了tomcat,而且還內(nèi)置了好多的東西,比如我們經(jīng)常使用的mq、redis等等一系列的東西,這個(gè)我們可以在spring.factories配置文件中看到,這個(gè)文件位于如下位置
大概內(nèi)容有下,篇幅有限,就不一一列舉了。
省略。。。 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ 省略。。。
那么回過(guò)頭來(lái),我們?cè)倏聪逻@個(gè)問(wèn)題,這些個(gè)類是如何被加載進(jìn)來(lái)的,我們知道SpingBoot有個(gè)注解是開啟自動(dòng)注解的@EnableAutoConfiguration,他就干這個(gè)事情的。他能夠激活SpringBoot內(nèi)建和自定義組件的自動(dòng)裝配特性。
那么,知道了這些,我們把這個(gè)之前修改后的類給改造一下,加上注解@EnableAutoConfiguration,看下執(zhí)行效果。
@RestController @RequestMapping("project/manager") @EnableAutoConfiguration public class ManagerController extends AbstractController {
運(yùn)行如下
從打印信息就能知道,服務(wù)器有了,只不過(guò)下面報(bào)錯(cuò),提示找不到bean,那這不就簡(jiǎn)單了么,他是不是就是沒(méi)有掃描到我們的包么,這里就其實(shí)可以在配置掃描包的注解繼續(xù)測(cè)試,我就懶的不測(cè)試了,直接去看@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è)注解其實(shí)就是一個(gè)組合注解,里面包含了
- 元注解:用來(lái)標(biāo)注他是一個(gè)注解的,jdk自帶的
- @SpringBootConfiguration:集成自@Configuration,表示是一個(gè)配置類
- @EnableAutoConfiguration:激活SpringBoot內(nèi)建和自定義組件的自動(dòng)裝配特性
- @ComponentScan:掃描注解,添加了排除參數(shù),指定排除了一些類
通過(guò)這里的實(shí)驗(yàn),我們可以得出結(jié)論:
primarySource參數(shù)能接收的類是一個(gè)配置類,同時(shí)要把符合掃描規(guī)則的類裝配到spring容器中,并且對(duì)SpringBoot內(nèi)置的一些類進(jìn)行自動(dòng)掃描到,而這里的@SpringBootApplication注解就是把這些特性都整合到了一起,作為了一個(gè)引導(dǎo)類而已。那么說(shuō)白了,primarySource他接受的其實(shí)就是一個(gè)配置類。
關(guān)于注解詳細(xì)知識(shí)的話,這里就聊這么多了,后面再詳細(xì)聊。
args參數(shù)
args是Java命令行參數(shù),我們?cè)贒OS中執(zhí)行Java程序的時(shí)候使用“java 文件名 args參數(shù)”。args這個(gè)數(shù)組可以接收到這些參數(shù)。這個(gè)是個(gè)基礎(chǔ)常識(shí)了。
以下我們將繼續(xù)跟蹤源碼進(jìn)行分析
我們繼續(xù)追run()方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args); }
這個(gè)方法干了兩個(gè)事情:
1、new SpringApplication()來(lái)創(chuàng)建對(duì)象
2、通過(guò)創(chuàng)建后的對(duì)象,調(diào)用對(duì)象里面的run()方法
以下我們將從這兩個(gè)地方進(jìn)行分析,本篇就先研究第一個(gè)
創(chuàng)建對(duì)象
我們先看下他是怎么創(chuàng)建對(duì)象的,創(chuàng)建了哪些對(duì)象,
public SpringApplication(Class<?>... primarySources) { this((ResourceLoader)null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { //資源加載器 this.resourceLoader = resourceLoader; //斷言 Assert.notNull(primarySources, "PrimarySources must not be null"); //對(duì)primarySources進(jìn)行存儲(chǔ)到LinkedHashSet this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //1、推斷web應(yīng)用類別 this.webApplicationType = WebApplicationType.deduceFromClasspath(); //2、加載Spring應(yīng)用上下文初始化 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //3、加載Spring應(yīng)用事件監(jiān)聽器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //4、推斷應(yīng)用引導(dǎo)類 this.mainApplicationClass = deduceMainApplicationClass(); }
下面研究下主要流程部分
1、推斷web應(yīng)用類別
推斷web應(yīng)用類型屬于SpringBoot應(yīng)用web類型的初始化過(guò)程。而該類型也可在SpringApplication構(gòu)造后,run方法執(zhí)行之前,通過(guò)setWebApplicationType(WebApplicationType webApplicationType)方法進(jìn)行調(diào)整。
在推斷Web應(yīng)用類型的過(guò)程中,由于當(dāng)前Spring應(yīng)用上下文尚未準(zhǔn)備(可在代碼執(zhí)行順序中看到),所以實(shí)現(xiàn)采用的是檢查檢查當(dāng)前ClassLoader下基準(zhǔn)Class的存在性判斷。
上源碼
this.webApplicationType = WebApplicationType.deduceFromClasspath(); 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"; static WebApplicationType deduceFromClasspath() { //當(dāng)DispatcherHandler存在,且DispatcherServlet、ServletContainer兩個(gè)不存在時(shí);換言之,SpringBoot僅依賴WebFlux存在時(shí),此時(shí)的應(yīng)用類型為REACTIVE if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } //當(dāng)Servlet和ConfigurableWebApplicationContext均不存在時(shí),當(dāng)前應(yīng)用為非Web應(yīng)用,即WebApplicationType.NONE,因?yàn)檫@些API均是Spring Web MVC必須的依賴 for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } //當(dāng)WebFlux和Spring Web MVC同時(shí)存在時(shí),Web應(yīng)用類型同樣是Servlet Web,即WebApplicationType.SERVLET return WebApplicationType.SERVLET; }
2、加載Spring應(yīng)用上下文初始化
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
此過(guò)程包含兩個(gè)動(dòng)作,依次為getSpringFactoriesInstances(ApplicationContextInitializer.class)和setInitializers方法。 先看第一個(gè)過(guò)程
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資源中配置的ApplicationContextInitializer實(shí)現(xiàn)類名單。 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //初始化 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
此處使用了Spring工廠加載機(jī)制方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)。加載了META-INF/spring.factories資源中配置的ApplicationContextInitializer實(shí)現(xiàn)類名單。
加載完成后使用createSpringFactoriesInstances方法對(duì)其進(jìn)行初始化。
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); for (String name : names) { try { //從類加載器中獲取指定類 Class<?> instanceClass = ClassUtils.forName(name, classLoader); //判斷instanceClass是不是type的子類 Assert.isAssignable(type, instanceClass); //根據(jù)以上獲取的類名創(chuàng)建類的實(shí)例 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; }
3、加載Spring應(yīng)用事件監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
此過(guò)程與2、加載上下文初始化基本類似。 只不過(guò)初始化的對(duì)象類型變成了ApplicationListener.class,setListeners方法也只是賦值而已
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) { this.listeners = new ArrayList<>(); this.listeners.addAll(listeners); }
4、推斷應(yīng)用引導(dǎo)類
this.mainApplicationClass = deduceMainApplicationClass(); 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; }
這里使用到了new RuntimeException().getStackTrace()來(lái)獲取堆棧信息,找到調(diào)用執(zhí)行的main方法,從而確定他的類。
這里有個(gè)疑問(wèn):他不是傳了primarySources數(shù)組,里面包含了類名么,怎么還用堆棧的方式去獲取,此外,這里的堆棧獲取也只能獲取一個(gè)調(diào)用的主main方法,他為啥還要傳一個(gè)Class數(shù)組呢?
具體咋獲取的,可以追下源碼,一直跟蹤他的父類Throwable,找到如下代碼
/** * Fills in the execution stack trace. This method records within this * {@code Throwable} object information about the current state of * the stack frames for the current thread. * * <p>If the stack trace of this {@code Throwable} {@linkplain * Throwable#Throwable(String, Throwable, boolean, boolean) is not * writable}, calling this method has no effect. * * @return a reference to this {@code Throwable} instance. * @see java.lang.Throwable#printStackTrace() */ public synchronized Throwable fillInStackTrace() { if (stackTrace != null || backtrace != null /* Out of protocol state */ ) { fillInStackTrace(0); stackTrace = UNASSIGNED_STACK; } return this; } private native Throwable fillInStackTrace(int dummy);
這里最后調(diào)用了native本地方法,去爬取線程堆棧信息,為運(yùn)行時(shí)棧做一份快照。
通過(guò)這個(gè)圖片,可以看到整個(gè)方法的調(diào)用鏈,從下往上看哦
本文僅為個(gè)人能力范圍內(nèi)理解,旨在分享出來(lái)和大家討論技術(shù),共同努力,共同進(jìn)步!
參考:《SpringBoot編程思想》
以上就是SpringBoot啟動(dòng)流程入口參數(shù)創(chuàng)建對(duì)象源碼分析的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot 啟動(dòng)參數(shù)創(chuàng)建的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue中computed計(jì)算屬性和data數(shù)據(jù)獲取方式
這篇文章主要介紹了Vue中computed計(jì)算屬性和data數(shù)據(jù)獲取方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問(wèn)題
這篇文章主要介紹了解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11SpringBoot整合Druid實(shí)現(xiàn)SQL監(jiān)控和數(shù)據(jù)庫(kù)密碼加密
Druid連接池是阿里巴巴開源的數(shù)據(jù)庫(kù)連接池項(xiàng)目,Druid連接池為監(jiān)控而生,內(nèi)置強(qiáng)大的監(jiān)控功能,監(jiān)控特性不影響性能,本文給大家介紹了SpringBoot整合Druid實(shí)現(xiàn)SQL監(jiān)控和數(shù)據(jù)庫(kù)密碼加密,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-06-06mybatis3中@SelectProvider傳遞參數(shù)方式
這篇文章主要介紹了mybatis3中@SelectProvider傳遞參數(shù)方式。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08如何通過(guò)javacv實(shí)現(xiàn)圖片去水印(附代碼)
這篇文章主要介紹了如何通過(guò)javacv實(shí)現(xiàn)圖片去水?。ǜ酱a),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07