環境
本文基於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 {
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.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) {
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);
}
(寫到這里,發現篇幅已經不短,就到這里作為第一篇吧。下篇繼續。)