博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot启动流程详解
阅读量:7221 次
发布时间:2019-06-29

本文共 38501 字,大约阅读时间需要 128 分钟。

hot3.png

本文系转载,自己mark一下,加深记忆和理解。原文地址:http://zhaox.github.io/java/2016/03/22/spring-boot-start-flow

 

环境

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

配置完成后,编写了代码如下:

@SpringBootApplicationpublic class Application {	public static void main(String[] args) {		SpringApplication.run(Application.class, args);	}}@RestControllerpublic class RootController {    public static final String PATH_ROOT = "/";    @RequestMapping(PATH_ROOT)    public String welcome() {        return "Welcome!";    }}

虽然只有几行代码,但是这已经是一个完整的Web程序,当访问url的path部分为”/”时,返回字符串”Welcome!”。

首先是一个非常普通的java程序入口,一个符合约定的静态main方法。在这个main方法中,调用了SpringApplication的静态run方法,并将Application类对象和main方法的参数args作为参数传递了进去。

然后是一个使用了两个Spring注解的RootController类,我们在main方法中,没有直接使用这个类。

SpringApplication类的静态run方法

以下代码摘自:org.springframework.boot.SpringApplicationpublic 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.SpringApplicationpublic 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.SpringApplicationprivate 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。

然后是initializers:

initializers成员变量,是一个ApplicationContextInitializer类型对象的集合。 顾名思义,ApplicationContextInitializer是一个可以用来初始化ApplicationContext的接口。

以下代码摘自:org.springframework.boot.SpringApplicationprivate List
> initializers;private void initialize(Object[] sources) { ... // 为成员变量initializers赋值 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); ...}public void setInitializers( Collection
> initializers) { this.initializers = new ArrayList
>(); this.initializers.addAll(initializers);}

可以看到,关键是调用getSpringFactoriesInstances(ApplicationContextInitializer.class),来获取ApplicationContextInitializer类型对象的列表。

以下代码摘自:org.springframework.boot.SpringApplicationprivate 
Collection
getSpringFactoriesInstances(Class
type) { return getSpringFactoriesInstances(type, new Class
[] {});}private
Collection
getSpringFactoriesInstances(Class
type, Class
[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set
names = new LinkedHashSet
( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List
instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances;}

在该方法中,首先通过调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)来获取所有Spring Factories的名字,然后调用createSpringFactoriesInstances方法根据读取到的名字创建对象。最后会将创建好的对象列表排序并返回。

以下代码摘自:org.springframework.core.io.support.SpringFactoriesLoaderpublic static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";public static List
loadFactoryNames(Class
factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration
urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List
result = new ArrayList
(); 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 Initializersorg.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.SpringApplicationprivate 
List
createSpringFactoriesInstances(Class
type, Class
[] parameterTypes, ClassLoader classLoader, Object[] args, Set
names) { List
instances = new ArrayList
(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,并说明了他们的作用。至于何时应用他们,且听后面慢慢分解。

d1c524388124f82f56a2ed5e8cee53b437e.jpg

 

接下来是成员变量listeners

以下代码摘自:org.springframework.boot.SpringApplicationprivate List
> listeners;private void initialize(Object[] sources) { ... // 为成员变量listeners赋值 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); ...}public void setListeners(Collection
> listeners) { this.listeners = new ArrayList
>(); 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 Listenersorg.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,并说明了他们的作用。至于他们何时会被触发,等事件出现时,我们再说明。

a30229a5ce56bb67251a7d088a975b920b2.jpg

最后是mainApplicationClass

以下代码摘自:org.springframework.boot.SpringApplicationprivate 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.SpringApplicationpublic 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.SpringApplicationprivate 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.SpringApplicationpublic 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 Listenersorg.springframework.boot.SpringApplicationRunListener=\org.springframework.boot.context.event.EventPublishingRunListener

可以看到,在我们的例子中加载的是org.springframework.boot.context.event.EventPublishingRunListener。我们看一看这个SpringApplicationRunListener究竟做了点什么工作了?

以下代码摘自:org.springframework.boot.context.event.EventPublishingRunListenerpublic EventPublishingRunListener(SpringApplication application, String[] args) {	this.application = application;	this.args = args;	this.multicaster = new SimpleApplicationEventMulticaster();	for (ApplicationListener
listener : application.getListeners()) { this.multicaster.addApplicationListener(listener); }}@Overridepublic void started() { publishEvent(new ApplicationStartedEvent(this.application, this.args));}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) { publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) { registerApplicationEventMulticaster(context);}@Overridepublic 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));}@Overridepublic void finished(ConfigurableApplicationContext context, Throwable exception) { publishEvent(getFinishedEvent(context, exception));}

EventPublishingRunListener在对象初始化时,将SpringApplication对象的成员变量listeners全都保存下来,然后在自己的public方法被调用时,发布相应的事件,或执行相应的操作。可以说这个RunListener是在SpringApplication对象的run方法执行到不同的阶段时,发布相应的event给SpringApplication对象的成员变量listeners中记录的事件监听器。

下图画出了SpringApplicationRunListeners相关的类结构,虽然我们的例子中只有一个SpringApplicationRunListener,但在这样的设计下,想要扩展是非常容易的!

bbd288a23ffafceb7b82e7a5d8d18c680fd.jpg

接下来,我们看一下在调用listeners的started方法。在我们的例子中,也就是发布了ApplicationStartedEvent时,我们已经加载的事件监听器都做了什么操作。至于其它事件的发布,我们按照代码执行的顺序在后面的章节在介绍。

  • ParentContextCloserApplicationListener不监听ApplicationStartedEvent,没有操作;
  • FileEncodingApplicationListener不监听ApplicationStartedEvent,没有操作;
  • AnsiOutputApplicationListener不监听ApplicationStartedEvent,没有操作;
  • ConfigFileApplicationListener不监听ApplicationStartedEvent,没有操作;
  • DelegatingApplicationListener不监听ApplicationStartedEvent,没有操作;
  • LiquibaseServiceLocatorApplicationListener监听ApplicationStartedEvent,会检查classpath中是否有liquibase.servicelocator.ServiceLocator并做相应操作;
以下代码摘自:org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {	if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) {		new LiquibasePresent().replaceServiceLocator();	}}

我们的例子中,classpath中不存在liquibase,所以不执行任何操作。

  • ClasspathLoggingApplicationListener监听ApplicationStartedEvent,会打印classpath到debug日志;
@Overridepublic 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中的类情况创建相应的日志系统对象,并执行一些初始化之前的操作;
@Overridepublic 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中的日志系统体系:

f6479e1d304b2c5ea73aaa6e08585c39d61.jpg

好了,ApplicationStartedEvent事件的处理这样就结束了。以后在介绍事件处理的时候,我们只介绍监听该事件的监听器的操作,而不监听的,就不再说明了。

创建并刷新ApplicationContext

以下代码摘自:org.springframework.boot.SpringApplicationpublic 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.DefaultApplicationArgumentsDefaultApplicationArguments(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.SimpleCommandLinePropertySourcepublic SimpleCommandLinePropertySource(String... args) {	super(new SimpleCommandLineArgsParser().parse(args));}

可以看到是把main函数的args参数当做一个PropertySource来解析。我们的例子中,args的长度为0,所以这里创建的DefaultApplicationArguments也没有实际的内容。

创建并配置ApplicationConext的Environment

以下代码摘自:org.springframework.boot.SpringApplicationprivate 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.SpringApplicationprivate Map
defaultProperties;private boolean addCommandLineProperties = true;private Set
additionalProfiles = new HashSet
();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
profiles = new LinkedHashSet
(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@Overridepublic 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@Overridepublic 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@Overridepublic 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@Overridepublic void onApplicationEvent(ApplicationEvent event) {	if (event instanceof ApplicationEnvironmentPreparedEvent) {		onApplicationEnvironmentPreparedEvent(				(ApplicationEnvironmentPreparedEvent) event);	}	if (event instanceof ApplicationPreparedEvent) {		onApplicationPreparedEvent(event);	}}private void onApplicationEnvironmentPreparedEvent(		ApplicationEnvironmentPreparedEvent event) {	List
postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); }}List
loadPostProcessors() { return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());}以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Environment Post Processorsorg.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.DelegatingApplicationListenerprivate static final String PROPERTY_NAME = "context.listener.classes";private SimpleApplicationEventMulticaster multicaster;@Overridepublic void onApplicationEvent(ApplicationEvent event) {	if (event instanceof ApplicationEnvironmentPreparedEvent) {		List
> delegates = getListeners( ((ApplicationEnvironmentPreparedEvent) event).getEnvironment()); if (delegates.isEmpty()) { return; } this.multicaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener
listener : delegates) { this.multicaster.addApplicationListener(listener); } } if (this.multicaster != null) { this.multicaster.multicastEvent(event); }}@SuppressWarnings("unchecked")private List
> getListeners( ConfigurableEnvironment env) { String classNames = env.getProperty(PROPERTY_NAME); List
> listeners = new ArrayList
>(); 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
) 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@Overridepublic 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.SpringApplicationprivate 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.SpringBootBannerprivate static final String[] BANNER = { "",		"  .   ____          _            __ _ _",		" /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\",		"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",		" \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )",		"  '  |____| .__|_| |_|_| |_\\__, | / / / /",		" =========|_|==============|___/=/_/_/_/" };private static final String SPRING_BOOT = " :: Spring Boot :: ";private static final int STRAP_LINE_SIZE = 42;@Overridepublic 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
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具体有什么功能。下图画出了它的继承体系。

0380fa4a15c05ab1869bd3c6548619e5736.jpg

可以看到我们加载的这个AnnotationConfigEmbeddedWebApplicationContext类,从名字就可以看出来,首先是一个WebApplicationContext实现了WebApplicationContext接口,然后是一个EmbeddedWebApplicationContext,这意味着它会自动创建并初始化一个EmbeddedServletContainer,同时还支持AnnotationConfig,会将使用注解标注的bean注册到ApplicationContext中。更详细的过程,后面在例子中再一一剖析。

可以看到在加载类对象AnnotationConfigEmbeddedWebApplicationContext之后,createApplicationContext方法中紧接着调用BeanUtils的instantiate方法来创建ApplicationContext对象,其代码如下:

以下代码摘自:org.springframework.beans.BeanUtilspublic static 
T instantiate(Class
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.AnnotationConfigEmbeddedWebApplicationContextpublic AnnotationConfigEmbeddedWebApplicationContext() {	this.reader = new AnnotatedBeanDefinitionReader(this);	this.scanner = new ClassPathBeanDefinitionScanner(this);}@Overridepublic 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
> getInitializers() { return asUnmodifiableOrderedSet(this.initializers);}private static
Set
asUnmodifiableOrderedSet(Collection
elements) { List
list = new ArrayList
(); list.addAll(elements); Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE); return new LinkedHashSet
(list);}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/u/1047398/blog/1931579

你可能感兴趣的文章
centos和redhat下 uwsgi配置
查看>>
Markdown 学习笔记
查看>>
vue-element-admin 多层路由问题
查看>>
Css问题 margin float 文档流 背景图底部充满
查看>>
JS match() 方法 使用
查看>>
关于shopee平台接口(php)对接示例
查看>>
BNU OJ 51000 BQG's Random String
查看>>
PAT (Advanced Level) 1044. Shopping in Mars (25)
查看>>
hdu 1531 King
查看>>
***R
查看>>
Linux 源码编译安装mysql
查看>>
取消手机端页面长按图片出现保存或者图片被打开的方法
查看>>
关于图片居中问题
查看>>
并发下的死锁问题
查看>>
Winserver下的Hyper-v “未在远程桌面会话中捕获到鼠标”
查看>>
oracle体系结构基础
查看>>
有关TCP和UDP 粘包 消息保护边界
查看>>
Mono为何能跨平台?聊聊CIL(MSIL)
查看>>
安装scrapy问题:-bash:scrapy:command not found
查看>>
CentOS7 重置root密码
查看>>