SpringBoot2 | SpringBoot啓動流程源碼分析(一)

微信公衆號:吉姆餐廳ak 學習更多源碼知識,歡迎關注。 java

在這裏插入圖片描述


SpringBoot2 | SpringBoot啓動流程源碼分析(一)react

SpringBoot2 | SpringBoot啓動流程源碼分析(二)web

SpringBoot2 | @SpringBootApplication註解 自動化配置流程源碼分析(三)spring

SpringBoot2 | SpringBoot Environment源碼分析(四)json

SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)數組

SpringBoot2 | SpringBoot監聽器源碼分析 | 自定義ApplicationListener(六)springboot

SpringBoot2 | 條件註解@ConditionalOnBean原理源碼深度解析(七)bash

SpringBoot2 | Spring AOP 原理源碼深度剖析(八)微信

SpringBoot2 | SpingBoot FilterRegistrationBean 註冊組件 | FilterChain 責任鏈源碼分析(九)app

SpringBoot2 | BeanDefinition 註冊核心類 ImportBeanDefinitionRegistrar (十)

SpringBoot2 | Spring 核心擴展接口 | 核心擴展方法總結(十一)


概述:

前陣子看到了SpringCloud社區的一個開源項目,主要是對服務發現加強的功能。研究項目的時候發現代碼簡練,優雅,最主要是spring ioc和aop特性應用的駕輕就熟。若非對源碼有深刻研究,不可能寫出這麼優秀的開源項目。另外在現有的springboot專欄中,大多數博文旨在應用,對一些中間件的整合之類,源碼分析的博客數量有限。鑑於以上兩方面,該系列應運而生。

該系列主要仍是Spring的核心源碼,不過目前Springboot大行其道,因此就從Springboot開始分析。最新版本是Springboot2.0.4,Spring5,因此新特性本系列後面也會着重分析。

整個系列會圍繞springboot啓動流程進行源碼分析,在整個流程中,會遇到一些核心類或者核心流程,會着重講解,因此篇幅可能會增多,作好準備。


源碼分析

首先是項目啓動類:

public static void main(String[] args) {
		SpringApplication.run(MarsApplication.class, args);
	}
複製代碼
public SpringApplication(Object... sources) {
		//初始化
		initialize(sources);
	}
複製代碼

初始化時,會加載META-INF/spring.factories文件:

private void initialize(Object[] sources) {
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
		//設置servlet環境
		this.webEnvironment = deduceWebEnvironment();
		//獲取ApplicationContextInitializer,也是在這裏開始首次加載spring.factories文件
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		//獲取監聽器,這裏是第二次加載spring.factories文件
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
複製代碼

來看一下deduceWebEnvironment()方法:

private WebApplicationType deduceWebApplicationType() {
		if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
				&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}
複製代碼

這裏主要是經過判斷REACTIVE相關的字節碼是否存在,若是不存在,則web環境即爲SERVLET類型。這裏設置好web環境類型,在後面會根據類型初始化對應環境。

ApplicationContextInitializer是spring組件spring-context組件中的一個接口,主要是spring ioc容器刷新以前的一個回調接口,用於處於自定義邏輯。 spring.factories文件中的實現類:

這裏寫圖片描述

這裏監聽器爲9個:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
複製代碼

還有1個爲:org.springframework.boot.autoconfigure.BackgroundPreinitializer 這10個監聽器會貫穿springBoot整個生命週期。稍後會介紹。


這裏先繼續後面的流程。來看一下run方法:

public ConfigurableApplicationContext run(String... args) {
		//時間監控
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		//java.awt.headless是J2SE的一種模式用於在缺乏顯示屏、鍵盤或者鼠標時的系統配置,不少監控工具如jconsole 須要將該值設置爲true,系統變量默認爲true
		configureHeadlessProperty();
		//獲取spring.factories中的監聽器變量,args爲指定的參數數組,默認爲當前類SpringApplication
		//第一步:獲取並啓動監聽器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			//第二步:構造容器環境
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			//設置須要忽略的bean
			configureIgnoreBeanInfo(environment);
			//打印banner
			Banner printedBanner = printBanner(environment);
			//第三步:建立容器
			context = createApplicationContext();
			//第四步:實例化SpringBootExceptionReporter.class,用來支持報告關於啓動的錯誤
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//第五步:準備容器
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			//第六步:刷新容器
			refreshContext(context);
			//第七步:刷新容器後的擴展接口
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			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;
	}
複製代碼
  • 第一步:獲取並啓動監聽器
  • 第二步:構造容器環境
  • 第三步:建立容器
  • 第四步:實例化SpringBootExceptionReporter.class,用來支持報告關於啓動的錯誤
  • 第五步:準備容器
  • 第六步:刷新容器
  • 第七步:刷新容器後的擴展接口

下面具體分析。


一:獲取並啓動監聽器

1)獲取監聽器

SpringApplicationRunListeners listeners = getRunListeners(args); 跟進getRunListeners方法:

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

上面能夠看到,args自己默認爲空,可是在獲取監聽器的方法中,getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)將當前對象做爲參數,該方法用來獲取spring.factories對應的監聽器:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
複製代碼

整個 springBoot 框架中獲取factories的方式統一以下:

@SuppressWarnings("unchecked")
	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文件到內存
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				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;
	}
複製代碼

上面經過反射獲取實例時會觸發EventPublishingRunListener的構造函數:

public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}
複製代碼

重點來看一下addApplicationListener方法:

@Override
	public void addApplicationListener(ApplicationListener<?> listener) {
		synchronized (this.retrievalMutex) {
			// Explicitly remove target for a proxy, if registered already,
			// in order to avoid double invocations of the same listener.
			Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
			if (singletonTarget instanceof ApplicationListener) {
				this.defaultRetriever.applicationListeners.remove(singletonTarget);
			}
			//內部類對象
			this.defaultRetriever.applicationListeners.add(listener);
			this.retrieverCache.clear();
		}
	}
複製代碼

上述方法定義在SimpleApplicationEventMulticaster父類AbstractApplicationEventMulticaster中。關鍵代碼爲this.defaultRetriever.applicationListeners.add(listener);,這是一個內部類,用來保存全部的監聽器。也就是在這一步,將spring.factories中的監聽器傳遞到SimpleApplicationEventMulticaster中。 繼承關係以下:

這裏寫圖片描述

2)啓動監聽器:

listeners.starting();,獲取的監聽器爲EventPublishingRunListener,從名字能夠看出是啓動事件發佈監聽器,主要用來發布啓動事件。

@Override
	public void starting() {
	//關鍵代碼,這裏是建立application啓動事件`ApplicationStartingEvent`
		this.initialMulticaster.multicastEvent(
				new ApplicationStartingEvent(this.application, this.args));
	}
複製代碼

EventPublishingRunListener這個是springBoot框架中最先執行的監聽器,在該監聽器執行started()方法時,會繼續發佈事件,也就是事件傳遞。這種實現主要仍是基於spring的事件機制。 繼續跟進SimpleApplicationEventMulticaster,有個核心方法:

@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			//獲取線程池,若是爲空則同步處理。這裏線程池爲空,還未沒初始化。
			Executor executor = getTaskExecutor();
			if (executor != null) {
			    //異步發送事件
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				//同步發送事件
				invokeListener(listener, event);
			}
		}
	}
複製代碼

這裏會根據事件類型ApplicationStartingEvent獲取對應的監聽器,在容器啓動以後執行響應的動做,有以下4種監聽器:

這裏寫圖片描述
這是springBoot啓動過程當中,第一處根據類型,執行監聽器的地方。根據發佈的事件類型從上述10種監聽器中選擇對應的監聽器進行事件發佈,固然若是繼承了 springCloud或者別的框架,就不止10個了。這裏選了一個 springBoot 的日誌監聽器來進行講解,核心代碼以下:

@Override
    public void onApplicationEvent(ApplicationEvent event) {
        //在springboot啓動的時候
        if (event instanceof ApplicationStartedEvent) {
            onApplicationStartedEvent((ApplicationStartedEvent) event);
        }
        //springboot的Environment環境準備完成的時候
        else if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        //在springboot容器的環境設置完成之後
        else if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent((ApplicationPreparedEvent) event);
        }
        //容器關閉的時候
        else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
                .getApplicationContext().getParent() == null) {
            onContextClosedEvent();
        }
        //容器啓動失敗的時候
        else if (event instanceof ApplicationFailedEvent) {
            onApplicationFailedEvent();
        }
    }
複製代碼

由於咱們的事件類型爲ApplicationEvent,因此會執行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot會在運行過程當中的不一樣階段,發送各類事件,來執行對應監聽器的對應方法。大同小異,別的監聽器執行流程這裏再也不贅述,後面會有單獨的詳解。 繼續後面的流程。


二:環境構建:

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); 跟進去該方法:

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		//獲取對應的ConfigurableEnvironment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//配置
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//發佈環境已準備事件,這是第二次發佈事件
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
複製代碼

來看一下getOrCreateEnvironment()方法,前面已經提到,environment已經被設置了servlet類型,因此這裏建立的是環境對象是StandardServletEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		if (this.webApplicationType == WebApplicationType.SERVLET) {
			return new StandardServletEnvironment();
		}
		return new StandardEnvironment();
	}
複製代碼

枚舉類WebApplicationType是springBoot2新增的特性,主要針對spring5引入的reactive特性。枚舉類型以下:

public enum WebApplicationType {
	//不須要再web容器的環境下運行,普通項目
	NONE,
	//基於servlet的web項目
	SERVLET,
	//這個是spring5版本開始的新特性
	REACTIVE
}
複製代碼

Environment接口提供了4種實現方式,StandardEnvironmentStandardServletEnvironmentMockEnvironmentStandardReactiveWebEnvironment,分別表明普通程序、Web程序、測試程序的環境、響應式web環境,具體後面會詳細講解。 這裏只須要知道在返回return new StandardServletEnvironment();對象的時候,會完成一系列初始化動做,主要就是將運行機器的系統變量和環境變量,加入到其父類AbstractEnvironment定義的對象MutablePropertySources中,MutablePropertySources對象中定義了一個屬性集合:

private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
複製代碼

執行到這裏,系統變量和環境變量已經被載入到配置文件的集合中,接下來就行解析項目中的配置文件。

來看一下listeners.environmentPrepared(environment);,上面已經提到了,這裏是第二次發佈事件。什麼事件呢? 顧名思義,系統環境初始化完成的事件。 發佈事件的流程上面已經講過了,這裏不在贅述。來看一下根據事件類型獲取到的監聽器:

這裏寫圖片描述

能夠看到獲取到的監聽器和第一次發佈啓動事件獲取的監聽器有幾個是重複的,這也驗證了監聽器是能夠屢次獲取,根據事件類型來區分具體處理邏輯。上面介紹日誌監聽器的時候已經提到。 主要來看一下ConfigFileApplicationListener,該監聽器很是核心,主要用來處理項目配置。項目中的 properties 和yml文件都是其內部類所加載。具體來看一下: 首先方法執行入口:

這裏寫圖片描述

首先仍是會去讀spring.factories 文件,List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();獲取的處理類有如下四種:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=	//一個@FunctionalInterface函數式接口
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,//爲springCloud提供的擴展類
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,//支持json環境變量
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor //springBoo2提供的一個包裝類,主要將`StandardServletEnvironment`包裝成`SystemEnvironmentPropertySourceEnvironmentPostProcessor`對象
複製代碼

在執行完上述三個監聽器流程後,ConfigFileApplicationListener會執行該類自己的邏輯。由其內部類Loader加載項目制定路徑下的配置文件:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
複製代碼

至此,項目的變量配置已所有加載完畢,來一塊兒看一下:

這裏寫圖片描述
這裏一共6個配置文件,取值順序由上到下。也就是說前面的配置變量會覆蓋後面同名的配置變量。項目配置變量的時候須要注意這點。


SpringBoot2 | SpringBoot啓動流程源碼分析(一)

SpringBoot2 | SpringBoot啓動流程源碼分析(二)

SpringBoot2 | @SpringBootApplication註解 自動化配置流程源碼分析(三)

SpringBoot2 | SpringBoot Environment源碼分析(四)

SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)

SpringBoot2 | SpringBoot監聽器源碼分析 | 自定義ApplicationListener(六)

SpringBoot2 | 條件註解@ConditionalOnBean原理源碼深度解析(七)

相關文章
相關標籤/搜索