SpringBoot啓動流程詳解

本文系轉載,本身mark一下,加深記憶和理解。原文地址:http://zhaox.github.io/java/2016/03/22/spring-boot-start-flowhtml

 

環境

本文基於Spring Boot版本1.3.3, 使用了spring-boot-starter-web。java

配置完成後,編寫了代碼以下:git

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

@RestController
public class RootController {

    public static final String PATH_ROOT = "/";

    @RequestMapping(PATH_ROOT)
    public String welcome() {
        return "Welcome!";
    }

}

雖然只有幾行代碼,可是這已是一個完整的Web程序,當訪問url的path部分爲」/」時,返回字符串」Welcome!」。github

首先是一個很是普通的java程序入口,一個符合約定的靜態main方法。在這個main方法中,調用了SpringApplication的靜態run方法,並將Application類對象和main方法的參數args做爲參數傳遞了進去。web

而後是一個使用了兩個Spring註解的RootController類,咱們在main方法中,沒有直接使用這個類。spring

SpringApplication類的靜態run方法

如下代碼摘自:org.springframework.boot.SpringApplication

public static ConfigurableApplicationContext run(Object source, String... args) {
	return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
	return new SpringApplication(sources).run(args);
}

在這個靜態方法中,建立SpringApplication對象,並調用該對象的run方法。數組

構造SpringApplication對象

如下代碼摘自:org.springframework.boot.SpringApplication

public SpringApplication(Object... sources) {
	initialize(sources);
}

private void initialize(Object[] sources) {
	// 爲成員變量sources賦值
	if (sources != null && sources.length > 0) {
		this.sources.addAll(Arrays.asList(sources));
	}
	this.webEnvironment = deduceWebEnvironment();
	setInitializers((Collection) getSpringFactoriesInstances(
			ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

構造函數中調用initialize方法,初始化SpringApplication對象的成員變量sources,webEnvironment,initializers,listeners,mainApplicationClass。sources的賦值比較簡單,就是咱們傳給SpringApplication.run方法的參數。剩下的幾個,咱們依次來看看是怎麼作的。服務器

首先是webEnvironment:

如下代碼摘自:org.springframework.boot.SpringApplication

private boolean webEnvironment; 

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

private void initialize(Object[] sources) {
	...
        // 爲成員變量webEnvironment賦值
        this.webEnvironment = deduceWebEnvironment();
	...
}

private boolean deduceWebEnvironment() {
	for (String className : WEB_ENVIRONMENT_CLASSES) {
		if (!ClassUtils.isPresent(className, null)) {
			return false;
		}
	}
	return true;
}

能夠看到webEnvironment是一個boolean,該成員變量用來表示當前應用程序是否是一個Web應用程序。那麼怎麼決定當前應用程序是否Web應用程序呢,是經過在classpath中查看是否存在WEB_ENVIRONMENT_CLASSES這個數組中所包含的類,若是存在那麼當前程序便是一個Web應用程序,反之則否則。 在本文的例子中webEnvironment的值爲true。oracle

而後是initializers:

initializers成員變量,是一個ApplicationContextInitializer類型對象的集合。 顧名思義,ApplicationContextInitializer是一個能夠用來初始化ApplicationContext的接口。app

如下代碼摘自:org.springframework.boot.SpringApplication

private List<ApplicationContextInitializer<?>> initializers;

private void initialize(Object[] sources) {
	...
	// 爲成員變量initializers賦值
	setInitializers((Collection) getSpringFactoriesInstances(
			ApplicationContextInitializer.class));
	...
}

public void setInitializers(
		Collection<? extends ApplicationContextInitializer<?>> initializers) {
	this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
	this.initializers.addAll(initializers);
}

能夠看到,關鍵是調用getSpringFactoriesInstances(ApplicationContextInitializer.class),來獲取ApplicationContextInitializer類型對象的列表。

如下代碼摘自:org.springframework.boot.SpringApplication

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
	return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
		Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<String>(
			SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
			classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

在該方法中,首先經過調用SpringFactoriesLoader.loadFactoryNames(type, classLoader)來獲取全部Spring Factories的名字,而後調用createSpringFactoriesInstances方法根據讀取到的名字建立對象。最後會將建立好的對象列表排序並返回。

如下代碼摘自:org.springframework.core.io.support.SpringFactoriesLoader

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	try {
		Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		List<String> result = new ArrayList<String>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
			String factoryClassNames = properties.getProperty(factoryClassName);
			result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
		}
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
				"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

能夠看到,是從一個名字叫spring.factories的資源文件中,讀取key爲org.springframework.context.ApplicationContextInitializer的value。而spring.factories的部份內容以下:

如下內容摘自spring-boot-1.3.3.RELEASE.jar中的資源文件META-INF/spring.factories

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer

能夠看到,最近的獲得的,是ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer這四個類的名字。

接下來會調用createSpringFactoriesInstances來建立ApplicationContextInitializer實例。

如下代碼摘自:org.springframework.boot.SpringApplication

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
		Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
		Set<String> names) {
	List<T> instances = new ArrayList<T>(names.size());
	for (String name : names) {
		try {
			Class<?> instanceClass = ClassUtils.forName(name, classLoader);
			Assert.isAssignable(type, instanceClass);
			Constructor<?> constructor = instanceClass.getConstructor(parameterTypes);
			T instance = (T) constructor.newInstance(args);
			instances.add(instance);
		}
		catch (Throwable ex) {
			throw new IllegalArgumentException(
					"Cannot instantiate " + type + " : " + name, ex);
		}
	}
	return instances;
}

因此在咱們的例子中,SpringApplication對象的成員變量initalizers就被初始化爲,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer這四個類的對象組成的list。

下圖畫出了加載的ApplicationContextInitializer,並說明了他們的做用。至於什麼時候應用他們,且聽後面慢慢分解。

 

接下來是成員變量listeners

如下代碼摘自:org.springframework.boot.SpringApplication

private List<ApplicationListener<?>> listeners;

private void initialize(Object[] sources) {
	...
	// 爲成員變量listeners賦值
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	...
}

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
	this.listeners = new ArrayList<ApplicationListener<?>>();
	this.listeners.addAll(listeners);
}

listeners成員變量,是一個ApplicationListener<?>類型對象的集合。能夠看到獲取該成員變量內容使用的是跟成員變量initializers同樣的方法,只不過傳入的類型從ApplicationContextInitializer.class變成了ApplicationListener.class。

看一下spring.factories中的相關內容:

如下內容摘自spring-boot-1.3.3.RELEASE.jar中的資源文件META-INF/spring.factories

# Application Listeners
org.springframework.context.ApplicationListener=\
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.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener

也就是說,在咱們的例子中,listener最終會被初始化爲ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener,DelegatingApplicationListener,LiquibaseServiceLocatorApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener這幾個類的對象組成的list。

下圖畫出了加載的ApplicationListener,並說明了他們的做用。至於他們什麼時候會被觸發,等事件出現時,咱們再說明。

最後是mainApplicationClass

如下代碼摘自:org.springframework.boot.SpringApplication

private Class<?> mainApplicationClass;

private void initialize(Object[] sources) {
	...
	// 爲成員變量mainApplicationClass賦值
	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;
}

在deduceMainApplicationClass方法中,經過獲取當前調用棧,找到入口方法main所在的類,並將其複製給SpringApplication對象的成員變量mainApplicationClass。在咱們的例子中mainApplicationClass便是咱們本身編寫的Application類。

SpringApplication對象的run方法

通過上面的初始化過程,咱們已經有了一個SpringApplication對象,根據SpringApplication類的靜態run方法一節中的分析,接下來會調用SpringApplication對象的run方法。咱們接下來就分析這個對象的run方法。

如下代碼摘自:org.springframework.boot.SpringApplication

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.started();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
		context = createAndRefreshContext(listeners, applicationArguments);
		afterRefresh(context, applicationArguments);
		listeners.finished(context, null);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
					.logStarted(getApplicationLog(), stopWatch);
		}
		return context;
	}
	catch (Throwable ex) {
		handleRunFailure(context, listeners, ex);
		throw new IllegalStateException(ex);
	}
}
  • 可變個數參數args便是咱們整個應用程序的入口main方法的參數,在咱們的例子中,參數個數爲零。

  • StopWatch是來自org.springframework.util的工具類,能夠用來方便的記錄程序的運行時間。

SpringApplication對象的run方法建立並刷新ApplicationContext,算是開始進入正題了。下面按照執行順序,介紹該方法所作的工做。

headless模式

如下代碼摘自:org.springframework.boot.SpringApplication

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private boolean headless = true;

public ConfigurableApplicationContext run(String... args) {
	...
	//設置headless模式
        configureHeadlessProperty();
	...
}

private void configureHeadlessProperty() {
	System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
			SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

其實是就是設置系統屬性java.awt.headless,在咱們的例子中該屬性會被設置爲true,由於咱們開發的是服務器程序,通常運行在沒有顯示器和鍵盤的環境。關於java中的headless模式,更多信息能夠參考這裏

SpringApplicationRunListeners

如下代碼摘自:org.springframework.boot.SpringApplication

public ConfigurableApplicationContext run(String... args) {
	...
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.started();
	/**
         * 建立並刷新ApplicationContext
         * context = createAndRefreshContext(listeners, applicationArguments); 
        **/
	listeners.finished(context, null);
	...
}

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

run方法中,加載了一系列SpringApplicationRunListener對象,在建立和更新ApplicationContext方法先後分別調用了listeners對象的started方法和finished方法, 並在建立和刷新ApplicationContext時,將listeners做爲參數傳遞到了createAndRefreshContext方法中,以便在建立和刷新ApplicationContext的不一樣階段,調用listeners的相應方法以執行操做。因此,所謂的SpringApplicationRunListeners實際上就是在SpringApplication對象的run方法執行的不一樣階段,去執行一些操做,而且這些操做是可配置的。

同時,能夠看到,加載SpringApplicationRunListener時,使用的是跟加載ApplicationContextInitializer和ApplicationListener時同樣的方法。那麼加載了什麼,就能夠從spring.factories文件中看到了:

如下內容摘自spring-boot-1.3.3.RELEASE.jar中的資源文件META-INF/spring.factories

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

能夠看到,在咱們的例子中加載的是org.springframework.boot.context.event.EventPublishingRunListener。咱們看一看這個SpringApplicationRunListener究竟作了點什麼工做了?

如下代碼摘自:org.springframework.boot.context.event.EventPublishingRunListener

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

@Override
public void started() {
	publishEvent(new ApplicationStartedEvent(this.application, this.args));
}

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
	publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
			environment));
}

@Override
public void contextPrepared(ConfigurableApplicationContext context) {
	registerApplicationEventMulticaster(context);
}

@Override
public void contextLoaded(ConfigurableApplicationContext context) {
	for (ApplicationListener<?> listener : this.application.getListeners()) {
		if (listener instanceof ApplicationContextAware) {
			((ApplicationContextAware) listener).setApplicationContext(context);
		}
		context.addApplicationListener(listener);
	}
	publishEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
	publishEvent(getFinishedEvent(context, exception));
}

EventPublishingRunListener在對象初始化時,將SpringApplication對象的成員變量listeners全都保存下來,而後在本身的public方法被調用時,發佈相應的事件,或執行相應的操做。能夠說這個RunListener是在SpringApplication對象的run方法執行到不一樣的階段時,發佈相應的event給SpringApplication對象的成員變量listeners中記錄的事件監聽器。

下圖畫出了SpringApplicationRunListeners相關的類結構,雖然咱們的例子中只有一個SpringApplicationRunListener,但在這樣的設計下,想要擴展是很是容易的!

接下來,咱們看一下在調用listeners的started方法。在咱們的例子中,也就是發佈了ApplicationStartedEvent時,咱們已經加載的事件監聽器都作了什麼操做。至於其它事件的發佈,咱們按照代碼執行的順序在後面的章節在介紹。

  • ParentContextCloserApplicationListener不監聽ApplicationStartedEvent,沒有操做;
  • FileEncodingApplicationListener不監聽ApplicationStartedEvent,沒有操做;
  • AnsiOutputApplicationListener不監聽ApplicationStartedEvent,沒有操做;
  • ConfigFileApplicationListener不監聽ApplicationStartedEvent,沒有操做;
  • DelegatingApplicationListener不監聽ApplicationStartedEvent,沒有操做;
  • LiquibaseServiceLocatorApplicationListener監聽ApplicationStartedEvent,會檢查classpath中是否有liquibase.servicelocator.ServiceLocator並作相應操做;
如下代碼摘自:org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
	if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) {
		new LiquibasePresent().replaceServiceLocator();
	}
}

咱們的例子中,classpath中不存在liquibase,因此不執行任何操做。

  • ClasspathLoggingApplicationListener監聽ApplicationStartedEvent,會打印classpath到debug日誌;
@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationStartedEvent) {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Application started with classpath: " + getClasspath());
	}
	...
}

private String getClasspath() {
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	if (classLoader instanceof URLClassLoader) {
		return Arrays.toString(((URLClassLoader) classLoader).getURLs());
	}
	return "unknown";
}

由於是debug級別的日誌,而SpringBoot的默認日誌級別是info級,因此咱們在控制檯不會看到classpath的輸出。

  • LoggingApplicationListener監聽ApplicationStartedEvent,會根據classpath中的類狀況建立相應的日誌系統對象,並執行一些初始化以前的操做;
@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationStartedEvent) {
		onApplicationStartedEvent((ApplicationStartedEvent) event);
	}
	...
}

private void onApplicationStartedEvent(ApplicationStartedEvent event) {
	this.loggingSystem = LoggingSystem
			.get(event.getSpringApplication().getClassLoader());
	this.loggingSystem.beforeInitialize();
}

咱們的例子中,建立的是org.springframework.boot.logging.logback.LogbackLoggingSystem類的對象,Logback是SpringBoot默認採用的日誌系統。下圖畫出了SpringBoot中的日誌系統體系:

好了,ApplicationStartedEvent事件的處理這樣就結束了。之後在介紹事件處理的時候,咱們只介紹監聽該事件的監聽器的操做,而不監聽的,就再也不說明了。

建立並刷新ApplicationContext

如下代碼摘自:org.springframework.boot.SpringApplication

public ConfigurableApplicationContext run(String... args) {
	...
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
		context = createAndRefreshContext(listeners, applicationArguments);
		afterRefresh(context, applicationArguments);
		...
	}
	catch (Throwable ex) {
		handleRunFailure(context, listeners, ex);
		throw new IllegalStateException(ex);
	}
}

首先是建立一個DefaultApplicationArguments對象,以後調用createAndRefreshContext方法建立並刷新一個ApplicationContext,最後調用afterRefresh方法在刷新以後作一些操做。

先來看看DefaultApplicationArguments吧:

如下代碼摘自:org.springframework.boot.DefaultApplicationArguments

DefaultApplicationArguments(String[] args) {
	Assert.notNull(args, "Args must not be null");
	this.source = new Source(args);
	this.args = args;
}

private static class Source extends SimpleCommandLinePropertySource {

	Source(String[] args) {
		super(args);
	}
	...
}

如下代碼摘自:org.springframework.core.env.SimpleCommandLinePropertySource

public SimpleCommandLinePropertySource(String... args) {
	super(new SimpleCommandLineArgsParser().parse(args));
}

能夠看到是把main函數的args參數當作一個PropertySource來解析。咱們的例子中,args的長度爲0,因此這裏建立的DefaultApplicationArguments也沒有實際的內容。

建立並配置ApplicationConext的Environment

如下代碼摘自:org.springframework.boot.SpringApplication

private ConfigurableEnvironment environment;

private boolean webEnvironment;

private ConfigurableApplicationContext createAndRefreshContext(
		SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	ConfigurableApplicationContext context;

	// 建立並配置Environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	listeners.environmentPrepared(environment);
	if (isWebEnvironment(environment) && !this.webEnvironment) {
		environment = convertToStandardEnvironment(environment);
	}

	...

	return context;
}

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	if (this.webEnvironment) {
		return new StandardServletEnvironment();
	}
	return new StandardEnvironment();
}

Spring Application的Environment表明着程序運行的環境,主要包含了兩種信息,一種是profiles,用來描述哪些bean definitions是可用的;一種是properties,用來描述系統的配置,其來源多是配置文件、JVM屬性文件、操做系統環境變量等等。

首先要調用getOrCreateEnvironment方法獲取一個Environment對象。在咱們的例子中,執行到此處時,environment成員變量爲null,而webEnvironment成員變量的值爲true,因此會建立一個StandardServletEnvironment對象並返回。

以後是調用configureEnvironment方法來配置上一步獲取的Environment對象,代碼以下:

如下代碼摘自:org.springframework.boot.SpringApplication

private Map<String, Object> defaultProperties;

private boolean addCommandLineProperties = true;

private Set<String> additionalProfiles = new HashSet<String>();

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

protected void configurePropertySources(ConfigurableEnvironment environment,
		String[] args) {
	MutablePropertySources sources = environment.getPropertySources();
	if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
		sources.addLast(
				new MapPropertySource("defaultProperties", this.defaultProperties));
	}
	if (this.addCommandLineProperties && args.length > 0) {
		String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
		if (sources.contains(name)) {
			PropertySource<?> source = sources.get(name);
			CompositePropertySource composite = new CompositePropertySource(name);
			composite.addPropertySource(new SimpleCommandLinePropertySource(
					name + "-" + args.hashCode(), args));
			composite.addPropertySource(source);
			sources.replace(name, composite);
		}
		else {
			sources.addFirst(new SimpleCommandLinePropertySource(args));
		}
	}
}

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
	environment.getActiveProfiles(); // ensure they are initialized
	// But these ones should go first (last wins in a property key clash)
	Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
	profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
	environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
}

configureEnvironment方法先是調用configurePropertySources來配置properties,而後調用configureProfiles來配置profiles。

configurePropertySources首先查看SpringApplication對象的成員變量defaultProperties,若是該變量非null且內容非空,則將其加入到Environment的PropertySource列表的最後。而後查看SpringApplication對象的成員變量addCommandLineProperties和main函數的參數args,若是設置了addCommandLineProperties=true,且args個數大於0,那麼就構造一個由main函數的參數組成的PropertySource放到Environment的PropertySource列表的最前面(這就能保證,咱們經過main函數的參數來作的配置是最優先的,能夠覆蓋其餘配置)。在咱們的例子中,因爲沒有配置defaultProperties且main函數的參數args個數爲0,因此這個函數什麼也不作。

configureProfiles首先會讀取Properties中key爲spring.profiles.active的配置項,配置到Environment,而後再將SpringApplication對象的成員變量additionalProfiles加入到Environment的active profiles配置中。在咱們的例子中,配置文件裏沒有spring.profiles.active的配置項,而SpringApplication對象的成員變量additionalProfiles也是一個空的集合,因此這個函數沒有配置任何active profile。

到如今,Environment就算是配置完成了。接下來調用SpringApplicationRunListeners類的對象listeners發佈ApplicationEnvironmentPreparedEvent事件:

如下代碼摘自:org.springframework.boot.context.event.EventPublishingRunListener

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
	publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
			environment));
}

好,如今來看一看咱們加載的ApplicationListener對象都有哪些響應了這個事件,作了什麼操做:

  • FileEncodingApplicationListener響應該事件,檢查file.encoding配置是否與spring.mandatory_file_encoding一致:
如下代碼摘自:org.springframework.boot.context.FileEncodingApplicationListener

@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
	RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
			event.getEnvironment(), "spring.");
	if (resolver.containsProperty("mandatoryFileEncoding")) {
		String encoding = System.getProperty("file.encoding");
		String desired = resolver.getProperty("mandatoryFileEncoding");
		if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
			logger.error("System property 'file.encoding' is currently '" + encoding
					+ "'. It should be '" + desired
					+ "' (as defined in 'spring.mandatoryFileEncoding').");
			logger.error("Environment variable LANG is '" + System.getenv("LANG")
					+ "'. You could use a locale setting that matches encoding='"
					+ desired + "'.");
			logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL")
					+ "'. You could use a locale setting that matches encoding='"
					+ desired + "'.");
			throw new IllegalStateException(
					"The Java Virtual Machine has not been configured to use the "
							+ "desired default character encoding (" + desired
							+ ").");
		}
	}
}

在咱們的例子中,由於沒有spring.mandatory_file_encoding的配置,因此這個響應方法什麼都不作。

  • AnsiOutputApplicationListener響應該事件,根據spring.output.ansi.enabled和spring.output.ansi.console-available對AnsiOutput類作相應配置:
如下代碼摘自:org.springframework.boot.context.config.AnsiOutputApplicationListener

@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
	RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
			event.getEnvironment(), "spring.output.ansi.");
	if (resolver.containsProperty("enabled")) {
		String enabled = resolver.getProperty("enabled");
		AnsiOutput.setEnabled(Enum.valueOf(Enabled.class, enabled.toUpperCase()));
	}

	if (resolver.containsProperty("console-available")) {
		AnsiOutput.setConsoleAvailable(
				resolver.getProperty("console-available", Boolean.class));
	}
}

咱們的例子中,這兩項配置都是空的,因此這個響應方法什麼都不作。

  • ConfigFileApplicationListener加載該事件,從一些約定的位置加載一些配置文件,並且這些位置是可配置的。
如下代碼摘自:org.springframework.boot.context.config.ConfigFileApplicationListener

@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
		onApplicationEnvironmentPreparedEvent(
				(ApplicationEnvironmentPreparedEvent) event);
	}
	if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent(event);
	}
}

private void onApplicationEnvironmentPreparedEvent(
		ApplicationEnvironmentPreparedEvent event) {
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	postProcessors.add(this);
	AnnotationAwareOrderComparator.sort(postProcessors);
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessEnvironment(event.getEnvironment(),
				event.getSpringApplication());
	}
}

List<EnvironmentPostProcessor> loadPostProcessors() {
	return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
			getClass().getClassLoader());
}


如下內容摘自spring-boot-1.3.3.RELEASE.jar中的資源文件META-INF/spring.factories

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor

能夠看到,ConfigFileApplicationListener從META-INF/spring.factories文件中讀取EnvironmentPostProcessor配置,加載相應的EnvironmentPostProcessor類的對象,並調用其postProcessEnvironment方法。在咱們的例子中,會加載CloudFoundryVcapEnvironmentPostProcessor和SpringApplicationJsonEnvironmentPostProcessor並執行,因爲咱們的例子中沒有CloudFoundry和Json的配置,因此這個響應,不會加載任何的配置文件到Environment中來。

  • DelegatingApplicationListener響應該事件,將配置文件中key爲context.listener.classes的配置項,加載在成員變量multicaster中:
如下內容摘自:org.springframework.boot.context.config.DelegatingApplicationListener

private static final String PROPERTY_NAME = "context.listener.classes";

private SimpleApplicationEventMulticaster multicaster;

@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
		List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
				((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
		if (delegates.isEmpty()) {
			return;
		}
		this.multicaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<ApplicationEvent> listener : delegates) {
			this.multicaster.addApplicationListener(listener);
		}
	}
	if (this.multicaster != null) {
		this.multicaster.multicastEvent(event);
	}
}

@SuppressWarnings("unchecked")
private List<ApplicationListener<ApplicationEvent>> getListeners(
		ConfigurableEnvironment env) {
	String classNames = env.getProperty(PROPERTY_NAME);
	List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<ApplicationListener<ApplicationEvent>>();
	if (StringUtils.hasLength(classNames)) {
		for (String className : StringUtils.commaDelimitedListToSet(classNames)) {
			try {
				Class<?> clazz = ClassUtils.forName(className,
						ClassUtils.getDefaultClassLoader());
				Assert.isAssignable(ApplicationListener.class, clazz, "class ["
						+ className + "] must implement ApplicationListener");
				listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils
						.instantiateClass(clazz));
			}
			catch (Exception ex) {
				throw new ApplicationContextException(
						"Failed to load context listener class [" + className + "]",
						ex);
			}
		}
	}
	AnnotationAwareOrderComparator.sort(listeners);
	return listeners;
}

咱們的例子中,由於沒有key爲context.listener.classes的Property,因此不會加載任何listener到該監聽器中。

  • LoggingApplicationListener響應該事件,並對在ApplicationStarted時加載的LoggingSystem作一些初始化工做:
如下代碼摘自:org.springframework.boot.logging.LoggingApplicationListener

@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationStartedEvent) {
		onApplicationStartedEvent((ApplicationStartedEvent) event);
	}
	else if (event instanceof ApplicationEnvironmentPreparedEvent) {
		onApplicationEnvironmentPreparedEvent(
				(ApplicationEnvironmentPreparedEvent) event);
	}
	else if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent((ApplicationPreparedEvent) event);
	}
	else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
			.getApplicationContext().getParent() == null) {
		onContextClosedEvent();
	}
}

private void onApplicationEnvironmentPreparedEvent(
		ApplicationEnvironmentPreparedEvent event) {
	if (this.loggingSystem == null) {
		this.loggingSystem = LoggingSystem
				.get(event.getSpringApplication().getClassLoader());
	}
	initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}

protected void initialize(ConfigurableEnvironment environment,
		ClassLoader classLoader) {
	LogFile logFile = LogFile.get(environment);
	setSystemProperties(environment, logFile);
	initializeEarlyLoggingLevel(environment);
	initializeSystem(environment, this.loggingSystem, logFile);
	initializeFinalLoggingLevels(environment, this.loggingSystem);
	registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

在咱們的例子中,是對加載的LogbackLoggingSystem作一些初始化工做。關於日誌系統更詳細的討論,值得再寫一篇文章,就不在這裏展開討論了。

打印banner

如下代碼摘自:org.springframework.boot.SpringApplication

private Banner banner;

private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

public static final String BANNER_LOCATION_PROPERTY = "banner.location";

public static final String BANNER_LOCATION_PROPERTY_VALUE = "banner.txt";

private static final Banner DEFAULT_BANNER = new SpringBootBanner();


private ConfigurableApplicationContext createAndRefreshContext(
		SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {

	...
	if (this.bannerMode != Banner.Mode.OFF) {
		printBanner(environment);
	}
	...
}

protected void printBanner(Environment environment) {
	Banner selectedBanner = selectBanner(environment);
	if (this.bannerMode == Banner.Mode.LOG) {
		try {
			logger.info(createStringFromBanner(selectedBanner, environment));
		}
		catch (UnsupportedEncodingException ex) {
			logger.warn("Failed to create String for banner", ex);
		}
	}
	else {
		selectedBanner.printBanner(environment, this.mainApplicationClass,
				System.out);
	}
}

private Banner selectBanner(Environment environment) {
	String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
			BANNER_LOCATION_PROPERTY_VALUE);
	ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
			: new DefaultResourceLoader(getClassLoader());
	Resource resource = resourceLoader.getResource(location);
	if (resource.exists()) {
		return new ResourceBanner(resource);
	}
	if (this.banner != null) {
		return this.banner;
	}
	return DEFAULT_BANNER;
}

private String createStringFromBanner(Banner banner, Environment environment)
		throws UnsupportedEncodingException {
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	banner.printBanner(environment, this.mainApplicationClass, new PrintStream(baos));
	String charset = environment.getProperty("banner.charset", "UTF-8");
	return baos.toString(charset);
}

printBanner方法中,首先會調用selectBanner方法獲得一個banner對象,而後判斷bannerMode的類型,若是是Banner.Mode.LOG,那麼將banner對象轉換爲字符串,打印一條info日誌,不然的話,調用banner對象的printbanner方法,將banner打印到標準輸出System.out。

在咱們的例子中,bannerMode是Banner.Mode.Console,並且也未曾提供過banner.txt這樣的資源文件。因此selectBanner方法中獲得到即是默認的banner對象,即SpringBootBanner類的對象:

如下代碼摘自:org.springframework.boot.SpringBootBanner

private static final String[] BANNER = { "",
		"  .   ____          _            __ _ _",
		" /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\",
		"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
		" \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )",
		"  '  |____| .__|_| |_|_| |_\\__, | / / / /",
		" =========|_|==============|___/=/_/_/_/" };

private static final String SPRING_BOOT = " :: Spring Boot :: ";

private static final int STRAP_LINE_SIZE = 42;

@Override
public void printBanner(Environment environment, Class<?> sourceClass,
		PrintStream printStream) {
	for (String line : BANNER) {
		printStream.println(line);
	}
	String version = SpringBootVersion.getVersion();
	version = (version == null ? "" : " (v" + version + ")");
	String padding = "";
	while (padding.length() < STRAP_LINE_SIZE
			- (version.length() + SPRING_BOOT.length())) {
		padding += " ";
	}

	printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
			AnsiColor.DEFAULT, padding, AnsiStyle.FAINT, version));
	printStream.println();
}

先打印個Spring的圖形,而後打印個Spring Boot的文本,再而後打印一下Spring Boot的版本。會在控制檯看到以下輸出:

如下內容是程序啓動後在console的輸出:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.3.RELEASE)

個人天。分析啓動流程這麼久,終於在屏幕有一行輸出了,不容易。

建立ApplicationContext

private Class<? extends ConfigurableApplicationContext> applicationContextClass;

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
		+ "annotation.AnnotationConfigApplicationContext";

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
		+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

private ConfigurableApplicationContext createAndRefreshContext(
		SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	ConfigurableApplicationContext context;

	...

	context = createApplicationContext();
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	applyInitializers(context);
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}

	...

	return context;
}


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);
}

createAndRefreshContext中調用createApplicationContext獲取建立ApplicationContext,能夠看到,當檢測到本次程序是一個web應用程序(成員變量webEnvironment爲true)的時候,就加載類DEFAULT_WEB_CONTEXT_CLASS,不然的話加載DEFAULT_CONTEXT_CLASS。咱們的例子是一個web應用程序,因此會加載DEFAULT_WEB_CONTEXT_CLASS,也就是org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext。咱們先來看一看這個AnnotationConfigEmbeddedWebApplicationContext具體有什麼功能。下圖畫出了它的繼承體系。

能夠看到咱們加載的這個AnnotationConfigEmbeddedWebApplicationContext類,從名字就能夠看出來,首先是一個WebApplicationContext實現了WebApplicationContext接口,而後是一個EmbeddedWebApplicationContext,這意味着它會自動建立並初始化一個EmbeddedServletContainer,同時還支持AnnotationConfig,會將使用註解標註的bean註冊到ApplicationContext中。更詳細的過程,後面在例子中再一一剖析。

能夠看到在加載類對象AnnotationConfigEmbeddedWebApplicationContext以後,createApplicationContext方法中緊接着調用BeanUtils的instantiate方法來建立ApplicationContext對象,其代碼以下:

如下代碼摘自:org.springframework.beans.BeanUtils

public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
	Assert.notNull(clazz, "Class must not be null");
	if (clazz.isInterface()) {
		throw new BeanInstantiationException(clazz, "Specified class is an interface");
	}
	try {
		return clazz.newInstance();
	}
	catch (InstantiationException ex) {
		throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
	}
	catch (IllegalAccessException ex) {
		throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
	}
}

經過調用Class對象的newInstance()方法來實例化對象,這等同於直接調用類的空的構造方法,因此咱們來看AnnotationConfigEmbeddedWebApplicationContext類的構造方法:

如下代碼摘自:org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext

public AnnotationConfigEmbeddedWebApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}

@Override
public void setEnvironment(ConfigurableEnvironment environment) {
	super.setEnvironment(environment);
	this.reader.setEnvironment(environment);
	this.scanner.setEnvironment(environment);
}

構造方法中初始化了兩個成員變量,類型分別爲AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner用以加載使用註解的bean定義。

這樣ApplicationContext對象就建立出來了,在createAndRefreshContext方法中建立了ApplicationContext對象以後會緊接着調用其setEnvironment將咱們以前準備好的Environment對象賦值進去。以後分別調用postProcessApplicationContext和applyInitializers作一些處理和初始化的操做。

先來看看postProcessApplicationContext:

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
	if (this.webEnvironment) {
		if (context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
			if (this.beanNameGenerator != null) {
				configurableContext.getBeanFactory().registerSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
						this.beanNameGenerator);
			}
		}
	}
	if (this.resourceLoader != null) {
		if (context instanceof GenericApplicationContext) {
			((GenericApplicationContext) context)
					.setResourceLoader(this.resourceLoader);
		}
		if (context instanceof DefaultResourceLoader) {
			((DefaultResourceLoader) context)
					.setClassLoader(this.resourceLoader.getClassLoader());
		}
	}
}

若是成員變量beanNameGenerator不爲Null,那麼爲ApplicationContext對象註冊beanNameGenerator bean。若是成員變量resourceLoader不爲null,則爲ApplicationContext對象設置ResourceLoader。咱們的例子中,這兩個成員變量都爲Null,因此什麼都不作。

以後是applyInitializers方法:

protected void applyInitializers(ConfigurableApplicationContext context) {
	for (ApplicationContextInitializer initializer : getInitializers()) {
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
				initializer.getClass(), ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		initializer.initialize(context);
	}
}

public Set<ApplicationContextInitializer<?>> getInitializers() {
	return asUnmodifiableOrderedSet(this.initializers);
}

private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
	List<E> list = new ArrayList<E>();
	list.addAll(elements);
	Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE);
	return new LinkedHashSet<E>(list);
}
相關文章
相關標籤/搜索