Spring Boot启动流程详解(一)
环境
本文基于 Spring Boot 版本 1.3.3, 使用了 spring-boot-starter-web。
配置完成后,编写了代码如下:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
public class RootController {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">PATH_ROOT</span> <span class="hljs-operator">=</span> <span class="hljs-string">"/"</span>;
<span class="hljs-meta">@RequestMapping(PATH_ROOT)</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">welcome</span><span class="hljs-params">()</span> {
<span class="hljs-keyword">return</span> <span class="hljs-string">"Welcome!"</span>;
}
}
虽然只有几行代码,但是这已经是一个完整的 Web 程序,当访问 url 的 path 部分为 "/" 时,返回字符串 "Welcome!"。
首先是一个非常普通的 java 程序入口,一个符合约定的静态 main 方法。在这个 main 方法中,调用了 SpringApplication 的静态 run 方法,并将 Application 类对象和 main 方法的参数 args 作为参数传递了进去。
然后是一个使用了两个 Spring 注解的 RootController 类,我们在 main 方法中,没有直接使用这个类。
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。
然后是 initializers:
initializers 成员变量,是一个 ApplicationContextInitializer 类型对象的集合。 顾名思义,ApplicationContextInitializer 是一个可以用来初始化 ApplicationContext 的接口。
以下代码摘自: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) {
<span class="hljs-built_in">super</span>(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;
<span class="hljs-comment">// 创建并配置Environment</span>
<span class="hljs-type">ConfigurableEnvironment</span> <span class="hljs-variable">environment</span> <span class="hljs-operator">=</span> getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
<span class="hljs-keyword">if</span> (isWebEnvironment(environment) && !<span class="hljs-built_in">this</span>.webEnvironment) {
environment = convertToStandardEnvironment(environment);
}
...
<span class="hljs-keyword">return</span> 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()));
}
<span class="hljs-keyword">if</span> (resolver.containsProperty(<span class="hljs-string">"console-available"</span>)) {
AnsiOutput.setConsoleAvailable(
resolver.getProperty(<span class="hljs-string">"console-available"</span>, 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) {
...
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.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);
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.logStartupInfo) {
logStartupInfo(context.getParent() == <span class="hljs-literal">null</span>);
logStartupProfileInfo(context);
}
...
<span class="hljs-keyword">return</span> 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);
}
(写到这里,发现篇幅已经不短,就到这里作为第一篇吧。下篇继续。)