Spring Boot 1.5x啓動全過程源碼分析(下)

目錄

1.創建並啓動計時監控類

4.獲取並啓動監聽器

5、根據運行監聽器和應用參數來準備 Spring 環境

6.創建容器

7.準備應用上下文

8.刷新應用上下文

9.應用上下文刷新後置處理

10.發佈應用上下文啓動完成事件

11.停止計時監控

13.返回上下文

總結


本文是對Springboot1.5x源碼中的run方法和相關源碼進行分析。

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
		return new SpringApplication(sources).run(args);
	}
	public ConfigurableApplicationContext run(String... args) {
        //1.設置時間監控
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
        //2.初始化應用上下文配置
		ConfigurableApplicationContext context = null;
		FailureAnalyzers analyzers = null;
        //3.java.awt.headless是J2SE的一種模式用於在缺少顯示屏、鍵盤或者鼠標時的系統配置,很多監控工具如jconsole 需要將該值設置爲true,系統變量默認爲true
		configureHeadlessProperty();
        //獲取spring.factories中的監聽器變量,args爲指定的參數數組,默認爲當前類SpringApplication
		//4:獲取並啓動監聽器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
            //5:構造容器環境
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
            //打印banner
			Banner printedBanner = printBanner(environment);
            //6:創建容器
			context = createApplicationContext();
			analyzers = new FailureAnalyzers(context);
            //7:準備應用上下文
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
            //8:刷新應用上下文
			refreshContext(context);
            //9:應用上下文刷新後置處理
			afterRefresh(context, applicationArguments);
            //10:發佈應用上下文啓動完成事件
			listeners.finished(context, null);
            //11.停止計時監控類
			stopWatch.stop();
            //12.輸出日誌記錄執行主類名、時間信息
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
            //13.返回上下文
			return context;
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, analyzers, ex);
			throw new IllegalStateException(ex);
		}
	}

1.創建並啓動計時監控類

StopWatch stopWatch = new StopWatch();
stopWatch.start();

關於StopWatch的相關源碼

    public void start() throws IllegalStateException {
		start("");
	}

	public void start(String taskName) throws IllegalStateException {
		if (this.running) {
			throw new IllegalStateException("Can't start StopWatch: it's already running");
		}
		this.running = true;
		this.currentTaskName = taskName;
		this.startTimeMillis = System.currentTimeMillis();
	}

首先記錄了當前任務的名稱,默認爲空字符串,然後記錄當前 Spring Boot 應用啓動的開始時間。

4.獲取並啓動監聽器

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

創建邏輯和之前實例化初始化器和監聽器的一樣,一樣調用的是 getSpringFactoriesInstances 方法來獲取配置的監聽器名稱並實例化所有的類。

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListeners(Log log,
        Collection<? extends SpringApplicationRunListener> listeners) {
    this.log = log;
    this.listeners = new ArrayList<>(listeners);
}

SpringApplicationRunListener 所有監聽器配置在 spring-boot-2.0.3.RELEASE.jar!/META-INF/spring.factories 這個配置文件裏面。

5、根據運行監聽器和應用參數來準備 Spring 環境

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

5.1關於prepartEnvironment的的源碼如下

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment   
        // 獲取(或者創建)應用環境
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        //  配置應用環境
		configureEnvironment(environment, applicationArguments.getSourceArgs());
        //  監聽裝配應用環境
		listeners.environmentPrepared(environment);
		if (!this.webEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		return environment;
	}

5.2 配置應用環境

protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

分兩種方式配置,一種是property sources;第二種是Profiles

6.創建容器

創建應用上下文

context = createApplicationContext();
protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				contextClass = Class.forName(this.webEnvironment
						? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
	}

其實就是根據不同的應用類型初始化不同的上下文應用類。

7.準備應用上下文

prepareContext(context, environment, listeners, applicationArguments,
        printedBanner);


private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
        //綁定環境到上下文
		context.setEnvironment(environment);
        //配置上下文的 bean 生成器及資源加載器
		postProcessApplicationContext(context);
        //爲上下文應用所有初始化器
		applyInitializers(context);
        //觸發所有 SpringApplicationRunListener 監聽器的 contextPrepared 事件方法
		listeners.contextPrepared(context);
        //記錄啓動日誌
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}

		// 註冊兩個特殊的單例bean
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}

		// 加載所有資源
		Set<Object> sources = getSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[sources.size()]));
        //觸發所有 SpringApplicationRunListener 監聽器的 contextLoaded 事件方法
		listeners.contextLoaded(context);
	}

8.刷新應用上下文

refreshContext(context);

這個主要是刷新 Spring 的應用上下文,刷新容器

9.應用上下文刷新後置處理

下面是關於afterRefresh的相關源碼和方法

afterRefresh(context, applicationArguments);

protected void afterRefresh(ConfigurableApplicationContext context,
			ApplicationArguments args) {
		callRunners(context, args);
	}


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

ApplicationRunner、CommandLineRunner這兩個接口會在容器啓動後進行調用,相當於開機啓動,可以實現多個。

根據類型的到bean,根據order排序,循環調用接口的run()方法。

10.發佈應用上下文啓動完成事件

 

listeners.finished(context, null);

public void finished(ConfigurableApplicationContext context, Throwable exception) {
		for (SpringApplicationRunListener listener : this.listeners) {
            //遍歷當前的監聽器,並觸發回調當前這個監聽器的方法
			callFinishedListener(listener, context, exception);
		}
	}

11.停止計時監控

stopWatch.stop();

public void stop() throws IllegalStateException {
    if (this.currentTaskName == null) {
        throw new IllegalStateException("Can't stop StopWatch: it's not running");
    }
    long lastTime = System.currentTimeMillis() - this.startTimeMillis;
    this.totalTimeMillis += lastTime;
    this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
    if (this.keepTaskList) {
        this.taskList.add(this.lastTaskInfo);
    }
    ++this.taskCount;
    this.currentTaskName = null;
}
```java
計時監聽器停止,並統計一些任務執行信息。

**12 輸出日誌記錄執行主類名、時間信息**
```java
if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass)
            .logStarted(getApplicationLog(), stopWatch);
}

13.返回上下文

return context;

總結

springboot1.5x和springboot2.x的啓動過程大同小異,在創建容器的過程中沒有太大的區別,在run方法中,最大的不同就是在啓動容器的方式和流程。如有雷同,純屬正常。

Run方法中的區別

1.5x 2.0x
listeners.finished(context, null);

 

listeners.started(context);
 callRunners(context, applicationArguments);

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章