SpringBoot啟動流程原理解析(二)


在上一章我們分析了SpingBoot啟動流程中實例化SpingApplication的過程。

return new SpringApplication(primarySources).run(args);
這篇文章咱么說下run()方法開始之后都做了那些事情。

繼續往下跟着源碼進入到run()這個是比較核心的一個方法了

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
                // 計時器開始
		stopWatch.start();
                // 創建啟動上下文對象
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
                // 配置Handless模式,是在缺少顯示屏、鍵盤或鼠標時的系統配置
                // 默認為true
		configureHeadlessProperty();
                //獲取並啟動監聽器
		SpringApplicationRunListeners listeners = getRunListeners(args);
                // 啟動監聽器
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                        // 准備環境
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                        // 忽略配置的bean
			configureIgnoreBeanInfo(environment);
                        // 打印banner,就是啟動的時候在控制台的spring圖案
			Banner printedBanner = printBanner(environment);
                        // 創建容器
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
                        // 准備應用上下文(spring容器前置處理)
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
                        // 刷新容器
			refreshContext(context);
                        // 刷新容器后的擴展接口(spring容器后置處理)
			afterRefresh(context, applicationArguments);
                        // 結束計時器並打印,這就是我們啟動后console的顯示的時間
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
                        // 發布監聽應用上下文啟動完成(發出啟動結束事件)
			listeners.started(context);
                        // 執行runner
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
                        // 異常處理,如果run過程發生異常
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
                        // 監聽應用上下文運行中
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
                // 返回最終構建的容器對象
		return context;
	}

接下來就對上面的關鍵步驟一一解釋

1. 獲取所有的監聽器


這段代碼我們比較熟悉了,上一篇咱么詳細介紹過,它的主要作用就是去META-INFO/spring.factories 中加載配置SpringApplicationRunListener的監聽器如下

顯然只有一個事件發布監聽器類,拿到了EventPublishingRunListener啟動事件發布監聽器,下一步就是開始啟動了listeners.starting();我們往下跟源碼看

	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		this.initialMulticaster
				.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}

啟動的時候實際上是又創建了一個ApplicationStartingEvent對象,其實就是監聽應用啟動事件。
其中 initialMulticaster是一個SimpleApplicationEventMuticaster

    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        // 獲取線程池,為每個監聽事件創建一個線程
        Executor executor = this.getTaskExecutor();
        // 根據ApplicationStartingEvent事件類型找到對應的監聽器,並迭代 
        Iterator var5 = this.getApplicationListeners(event, type).iterator();

        while(var5.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var5.next();
            if (executor != null) {
                // 
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

    }

2.准備環境

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
                // 這里我們加入了web依賴所以是一個servlet容器
		ConfigurableEnvironment environment = getOrCreateEnvironment();
                // 配置環境
		configureEnvironment(environment, applicationArguments.getSourceArgs());
                // 環境准備完成
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		configureAdditionalProfiles(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

由於我們是添加了web的依賴 getOrCreateEnvironment()返回的是一個standardservletEnviroment 標准的servlet環境

2.1 配置環境

	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		if (this.addConversionService) {
                        // 嵌入式的轉換器
			ConversionService conversionService = ApplicationConversionService.getSharedInstance();
			environment.setConversionService((ConfigurableConversionService) conversionService);
		}
                // 配置屬性資源文件
		configurePropertySources(environment, args);
                // 配置文件
		configureProfiles(environment, args);
	}

應用嵌入的轉換器ApplicationConversionService

	public static void configure(FormatterRegistry registry) {
		DefaultConversionService.addDefaultConverters(registry);
		DefaultFormattingConversionService.addDefaultFormatters(registry);
                // 格式轉換
		addApplicationFormatters(registry);
                // 類型轉換
		addApplicationConverters(registry);
	}

        ===============格式轉換=================
	public static void addApplicationFormatters(FormatterRegistry registry) {
		registry.addFormatter(new CharArrayFormatter());
		registry.addFormatter(new InetAddressFormatter());
		registry.addFormatter(new IsoOffsetFormatter());
	}


        ========================類型轉換===================
	public static void addApplicationConverters(ConverterRegistry registry) {
		addDelimitedStringConverters(registry);
		registry.addConverter(new StringToDurationConverter());
		registry.addConverter(new DurationToStringConverter());
		registry.addConverter(new NumberToDurationConverter());
		registry.addConverter(new DurationToNumberConverter());
		registry.addConverter(new StringToPeriodConverter());
		registry.addConverter(new PeriodToStringConverter());
		registry.addConverter(new NumberToPeriodConverter());
		registry.addConverter(new StringToDataSizeConverter());
		registry.addConverter(new NumberToDataSizeConverter());
		registry.addConverter(new StringToFileConverter());
		registry.addConverter(new InputStreamSourceToByteArrayConverter());
		registry.addConverterFactory(new LenientStringToEnumConverterFactory());
		registry.addConverterFactory(new LenientBooleanToEnumConverterFactory());
		if (registry instanceof ConversionService) {
			addApplicationConverters(registry, (ConversionService) registry);
		}
	}
    

2.2 環境准備完成

同上面啟動監聽事件,這次的環境准備也是同樣的代碼

	@Override
	public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(
                                // 創建一個應用環境准備事件對象
				new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
	}

debug進去之后代碼跟AppLicationstrigevent 事件對象是一樣的。不再贅述。
不過這里是7個監聽器對象

3.配置忽略的bean

configureIgnoreBeanInfo(environment);

4.打印banner

這是SpringBoot默認的啟動時的圖標
Banner printedBanner = printBanner(environment);

這個是可以自定義的,也可以是圖篇或是文本文件中的圖形

5.創建容器

緊接着上一篇,接下來就是創建容器

	protected ConfigurableApplicationContext createApplicationContext() {
		return this.applicationContextFactory.create(this.webApplicationType);
	}

6.准備應用上下文

	private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
                // 設置環境參數
		context.setEnvironment(environment);
                // 設置后處理應用上下文
		postProcessApplicationContext(context);
                //把從spring.factories中加載的org.springframework.bt.context.ConfigurationwarningsApplicationContextIitiaLizer,進行初始化操作
		applyInitializers(context);
                //EventPubLishingRunListener發布應用上下文事件 
		listeners.contextPrepared(context);
                // 打印啟動日志
		bootstrapContext.close(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
                
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
                         //注冊一個字是springAppLicationArguments單例的bean 
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources 獲取所有資源
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
                // 創建BeanDefinitionLoader加載器加載注冊所有的資源 
		load(context, sources.toArray(new Object[0]));
                // 同之前,發布應用上下文 加載事件 
		listeners.contextLoaded(context);
	}

7.刷新應用上下文

刷新應用上下文就進入了spring的源碼了

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            // Prepare this context for refreshing.
            //准備刷新上下文
            this.prepareRefresh();
            // Tetl the subclass to refresh the internal bean facto
            // 通知子類刷新內部工廠
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            // 准備Bean工廠
            this.prepareBeanFactory(beanFactory);

            try {
                 // Allows post-processing of the bean factory in contex t subc lasses.
                // 允許在上下文子類中對bean工廠進行后處理。
                // Invoke factory processors registered as beans in the context,
                this.postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                this.invokeBeanFactoryPostProcessors(beanFactory);
                // 注冊后置處理器。
                this.registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                // 初始化信息源
                this.initMessageSource();
                // 初始化上下文事件發布器
                this.initApplicationEventMulticaster();
                // 初始化其他自定義bean 
                this.onRefresh();
                // 注冊監聽器
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                //完成刷新,清緩存,初始化生命周期,事件發布等
                this.finishRefresh();
            } catch (BeansException var10) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
                }
                // 銷毀bean 
                this.destroyBeans();
                // Reset 'active'flag.
                this.cancelRefresh(var10);
                throw var10;
            } finally {
                this.resetCommonCaches();
                contextRefresh.end();
            }

        }
    }

刷新的代碼有點深,也是在這時創建了Tomcat對象,這也是SpringBoot** 一鍵啟動**web工程的關鍵


創建了Tomcat對象,並設置參數

	@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); 
                // 返回TomcatWebServer服務
		return getTomcatWebServer(tomcat);
	}

8.刷新后處理

afterReftesh(); //是個一空實現,留着后期擴展

	/**
	 * Called after the context has been refreshed.
	 * @param context the application context
	 * @param args the application arguments
	 */
	protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
	}

9.發布監聽應用啟動事件

	@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}

這里是調用context.publishEvent()方法,發布應用啟動事件ApplicationStartedEvent.

10.執行Runner

獲取所有的ApplicationRuner和CommandLineRunner來初始化一些參數,callRuner(是一個回調函數)

	private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

11.發布上下文准備完成的事件

listeners.running(context);

	@Override
	public void running(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
		AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
	}

這段代碼看上去似成相識,前面有很多類似的代碼,不同的是這里上下文准備完成之后發布了一個ApplicationReadyEvent事件,聲明一下應用上下文准備完成。
小結
這篇主要是介紹了SpringBoot啟動過程中run()的這個過程。從中我們也可以發現一些非常好的編碼習慣,大家可以在日常的工作中從模仿到內化,慢慢變成自己的東西。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM