最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Springboot啟動流程詳細分析

 更新時間:2022年12月23日 11:37:02   作者:起風哥  
這篇文章主要介紹了SpringBoot啟動過程的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

springboot啟動是通過一個main方法啟動的,代碼如下

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

從該方法我們一路跟進去,進入SpringApplication的構(gòu)造函數(shù),我們可以看到如下代碼primarySources,為我們從run方法塞進來的主類,而resourceLoader此處為null,是通過構(gòu)造函數(shù)重載進來,意味著這里還有其它方式的用法,先繞過。

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還是啥也不是即當前非web容器
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//此處進入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 上一步傳進來的為AppClassLoader,如果對classLoader不了解需要去看看java的類加載機制,以及雙親委托機制,此處的資源加載也是個雙親委托機制。
			//此處如果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);
				//讀取對應文件內(nèi)容,作為一個properties對象
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				//解析文件內(nèi)容,并將之放入一個LinkedMultiValueMap中,key就是spring.factories中等號前面的部分,值是后面部分根據(jù)都好組成的list。并放入緩存?zhèn)溆?,緩存的key為對應的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 的集合

設置該當前類的initializers集合,后面的Listeners 集合也是一樣的過程。只不過,后一次獲取是從上一次的緩存中取的。

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}

設置完initializer和listener集合后,進行主函數(shù)推斷

接著看代碼,此處對主函數(shù)的推斷非常的巧妙,就是利用虛擬機運行時已被入棧的所有鏈路一路追蹤到方法名為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 設置了主類集合
  • this.webApplicationType 設置了web容器類型
  • setInitializers 設置了ApplicationContextInitializer
  • setListeners 設置了ApplicationListener
  • mainApplicationClass 設置了入口類
  • 加載并且解析了所有的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<>();
		//告訴程序當前為無頭模式,沒有外設,如果需要調(diào)用外設接口需要你自己模擬,這些內(nèi)容再awt包中做了,所以你只要告訴它有沒有外設就可以了
		configureHeadlessProperty();
		//繼續(xù)從spring.factories中獲取對應的對象,此時listeners中有一個默認實現(xiàn)EventPublishingRunListener,如有需要這里是可進行擴展
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//遍歷所有的listeners并調(diào)用它的starting方法,廣播啟動過程中的事件,注意這里是個廣播器不是監(jiān)聽器,名字有點不好理解,實現(xiàn)了
		//ApplicationListener的接口就接收到對應事件之后執(zhí)行對應操作,而我們前面也有設置了個listeners集合,為ApplicationListener
		//此處名字起的讓人容易誤解,明明是個廣播器非要叫RunListener
		listeners.starting();
		try {
			//進行控制臺參數(shù)解析,具體如何解析,此處不展開,后續(xù)對配置文件解析的文章在詳細介紹
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			//此處配置個內(nèi)省beanInfo時是否緩存的開關,true就緩存,false,就不緩存		
			configureIgnoreBeanInfo(environment);
			//打印banner,這個不介紹了比較簡單的東西
			Banner printedBanner = printBanner(environment);
			//此處開始創(chuàng)建容器,根據(jù)給定的容器類型也就是上面獲取到的webApplicationType創(chuà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起來,然后通過解析器鏈進行解析。這個也是從spring.factories中獲取的
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//上面創(chuàng)建了容器,這邊對容器做一些初始化操作
			//1、設置環(huán)境變量env
			//2、執(zhí)行postProcessApplicationContext 準備beanNameGenerator,resourceLoader,ApplicationConversionService
			//3、調(diào)用前面獲取到的ApplicationContextInitializer
			//4、廣播容器準備完成事件
			//5、添加了一個制定的單例就是banner打印用的
			//6、在加載前,設置是否允許bean被覆蓋屬性,默認false
			//7、開始加載,入口為main函數(shù)傳入的primarySources,先創(chuàng)建個BeanDefinitionLoader,并將第二步準備的實例設置給他,
			//最后由BeanDefinitionLoader.load()承當所有的加載動作,加載過程也很長,先按下不表。
			//8、廣播容器加載事件
			//到此容器準備完成		
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			//開始執(zhí)行spring的refresh方法,此處不多介紹了
			refreshContext(context);
			//容器refresh完成后留了個擴展,也不知道是不是擴展,這個方法需要去自定義啟動類,有點奇怪
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			//然后廣播啟動完成事件
			listeners.started(context);
			//最后執(zhí)行所有的runners,也就是實現(xiàn)了ApplicationRunner接口的類是最后執(zhí)行,所以此時你可以做一些其它的動作,比如注冊到注冊中心,比如啟動netty等等。
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}
		//在廣播一個運行中的事件
		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

上述代碼中在prepareContext階段完成了第一階段的beanDefinition注冊,此處僅注冊了第一bean 通過主類傳進去的那個類。之后再refresh階段,通過解析該類進行第二輪的beandefinition注冊。這一部分屬于spring-context的內(nèi)容了。

在refresh階段,因為主類作為一個配置類,自然也是需要進行一輪解析的,所以接下來的步驟就是解析@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) })

內(nèi)容解析:

  • @SpringBootConfiguration 等同于@Configuration
  • @EnableAutoConfiguration
  @Target(ElementType.TYPE)
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @Inherited
  @AutoConfigurationPackage
  @Import(AutoConfigurationImportSelector.class)
  public @interface EnableAutoConfiguration {

此處導入了一個AutoConfigurationImportSelector ,同時還通過@AutoConfigurationPackage導入了一個AutoConfigurationPackages.Registrar.class

  • 接下來是一個@ComponentScan注解

而我們知道在spring的配置類(單一類)的解析過程中的順序是

  • 先解析Component系列的注解
  • 再解析@PropertySource
  • 接著解析@ComponentScan與@ComponentScans注解
  • 接著解析@Import
  • 接著解析ImportResource
  • 最后解析@Bean注解

而再Import中又分為幾種類型

ImportSelector

DeferredImportSelector

ImportBeanDefinitionRegistrar

普通的import類

他們的加載順序為,普通的類–>importSelector–deferredImportSelector–>ImportResource–>ImportBeanDefinitionRegistrar

綜上所述spring.factories聲明的配置類看來也不是墊底解析的,所以如果有遇到需要順序的場景可以參照這個順序來聲明即可。

到此這篇關于Springboot啟動流程詳細分析的文章就介紹到這了,更多相關Springboot啟動流程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 淺談MyBatis原生批量插入的坑與解決方案

    淺談MyBatis原生批量插入的坑與解決方案

    本文主要介紹了淺談MyBatis原生批量插入的坑與解決方案,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • SpringBoot中的Condition包下常用條件依賴注解案例介紹

    SpringBoot中的Condition包下常用條件依賴注解案例介紹

    這篇文章主要介紹了SpringBoot中的Condition包下常用條件依賴注解案例,文章基于Java的相關資料展開主題詳細內(nèi)容,需要的小伙伴可以參考一下
    2022-04-04
  • Java中集合LinkedList的原理與使用方法

    Java中集合LinkedList的原理與使用方法

    這篇文章主要給大家介紹了關于Java中集合LinkedList的原理與使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-06-06
  • Spring MVC如何設置響應

    Spring MVC如何設置響應

    本文介紹了如何在Spring框架中設置響應,并通過不同的注解返回靜態(tài)頁面、HTML片段和JSON數(shù)據(jù),此外,還講解了如何設置響應的狀態(tài)碼和Header
    2025-01-01
  • Spring Boot中@ConditionalOnProperty的使用方法

    Spring Boot中@ConditionalOnProperty的使用方法

    這篇文章主要給大家介紹了關于Spring Boot中@ConditionalOnProperty的使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者使用Spring Boot具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-12-12
  • Feign自定義重試策略及超時時間詳解

    Feign自定義重試策略及超時時間詳解

    這篇文章主要為大家介紹了Feign自定義重試策略及超時時間詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • Spring注解中@Configuration和@Component到底有啥區(qū)別

    Spring注解中@Configuration和@Component到底有啥區(qū)別

    之前一直搞不清@Component和@Configuration這兩個注解到底有啥區(qū)別,一直認為被這兩修飾的類可以被Spring實例化嘛,最近終于弄明白了,這篇文章主要給大家介紹了關于Spring注解中@Configuration和@Component到底有啥區(qū)別的相關資料,需要的朋友可以參考下
    2023-04-04
  • Springboot MDC+logback實現(xiàn)日志追蹤的方法

    Springboot MDC+logback實現(xiàn)日志追蹤的方法

    MDC(Mapped Diagnostic Contexts)映射診斷上下文,該特征是logback提供的一種方便在多線程條件下的記錄日志的功能,這篇文章主要介紹了Springboot MDC+logback實現(xiàn)日志追蹤的方法,需要的朋友可以參考下
    2024-04-04
  • Java面試之如何實現(xiàn)10億數(shù)據(jù)判重

    Java面試之如何實現(xiàn)10億數(shù)據(jù)判重

    當數(shù)據(jù)量比較大時,使用常規(guī)的方式來判重就不行了,所以這篇文章小編主要來和大家介紹一下Java實現(xiàn)10億數(shù)據(jù)判重的相關方法,希望對大家有所幫助
    2024-02-02
  • java 靜態(tài)鏈表實現(xiàn)示例詳解

    java 靜態(tài)鏈表實現(xiàn)示例詳解

    這篇文章主要為大家介紹了java 靜態(tài)鏈表實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06

最新評論

余干县| 温宿县| 普兰店市| 甘肃省| 共和县| 扶绥县| 平泉县| 莲花县| 普兰县| 襄垣县| 永安市| 西林县| 铜川市| 万载县| 沅陵县| 婺源县| 十堰市| 清新县| 紫金县| 苍山县| 绥芬河市| 五河县| 灵寿县| 西盟| 民丰县| 丹棱县| 慈利县| 宁城县| 平乡县| 隆子县| 鹤山市| 南开区| 新竹市| 香格里拉县| 南宁市| 棋牌| 龙游县| 抚松县| 紫金县| 阿勒泰市| 新兴县|