spring boot 2.0 源码分析(一)
在学习 spring boot 2.0 源码之前,我们先利用 spring initializr 快速地创建一个基本的简单的示例:
1. 先从创建示例中的 main 函数开始读起:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
-
spring boot 2.0 源码分析
-
author lizongshen
-
date 2018/06/02
*/
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2. 在这里我们可以看到,spring boot 是通过 SpringApplication.run 这个函数来进行启动的,其中 args 可以传递启动时需要的个性化参数。跳转到源码中继续一探究竟:
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
3. 在这里我们看到,其把 primarySource 这个参数包装成数组,跳转到了另外一个同样的方法中。
小发现:SpringApplication.run() 函数是允许同时启动多个 Application 的。
接着往下读
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
4. 在这段代码中,我们可以看到 spring boot 把我们所使用的静态方法,创建出了一个 SpringApplication 的实例,并启动了实例中的 run 方法。
小知识:根据这个发现,我们也可以在 main 函数中,自己创建 SpringApplication 的实例,然后调用实例方法 run。
我们来看一下 SpringApplication 的构造函数期间都干了些什么事:
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #SpringApplication(ResourceLoader, Class...)
* @see #setSources(Set)
*/
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
<span class="hljs-comment">/**
* Create a new {<span class="hljs-doctag">@link</span> SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {<span class="hljs-doctag">@link</span> SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {<span class="hljs-doctag">@link</span> #run(String...)}.
* <span class="hljs-doctag">@param</span> resourceLoader the resource loader to use
* <span class="hljs-doctag">@param</span> primarySources the primary bean sources
* <span class="hljs-doctag">@see</span> #run(Class, String[])
* <span class="hljs-doctag">@see</span> #setSources(Set)
*/</span>
<span class="hljs-meta">@SuppressWarnings({ "unchecked", "rawtypes" })</span>
<span class="hljs-keyword">public</span> <span class="hljs-title function_">SpringApplication</span><span class="hljs-params">(ResourceLoader resourceLoader, Class<?>... primarySources)</span> {
<span class="hljs-built_in">this</span>.resourceLoader = resourceLoader;
Assert.notNull(primarySources, <span class="hljs-string">"PrimarySources must not be null"</span>);
<span class="hljs-built_in">this</span>.primarySources = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedHashSet</span><>(Arrays.asList(primarySources));
<span class="hljs-built_in">this</span>.webApplicationType = deduceWebApplicationType();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
<span class="hljs-built_in">this</span>.mainApplicationClass = deduceMainApplicationClass();
}
在这里主要是初始化了 SpringApplication 的私有属性,在构造的过程中,调用了另外一个构造函数,并传递了 ResourceLoader 的参数。
小知识:通过 ResourceLoader 这个参数,我们看到如果自定义 SpringApplication 在初始化过程中,是可以通过 ResourceLoader 来引入自定义资源的。
接着往下看,来看看 run 函数的真面目
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
<span class="hljs-keyword">try</span> {
listeners.running(context);
}
<span class="hljs-keyword">catch</span> (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, <span class="hljs-literal">null</span>);
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalStateException</span>(ex);
}
<span class="hljs-keyword">return</span> context;
}
这段代码很长,我们先来一点一点地分析。
1. 通过 configureHeadlessProperty(); 这行代码,配置属性:
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
2. 通过 SpringApplicationRunListeners listeners = getRunListeners(args); 这行代码获取了监听器:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
3. 通过 listeners.starting(); 这行代码启动监听器:
public void starting() {
Iterator var1 = this.listeners.iterator();
<span class="hljs-keyword">while</span>(var1.hasNext()) {
<span class="hljs-type">SpringApplicationRunListener</span> <span class="hljs-variable">listener</span> <span class="hljs-operator">=</span> (SpringApplicationRunListener)var1.next();
listener.starting();
}
}
4. 通过 context = this.createApplicationContext(); 创建了容器
/**
* Strategy method used to create the {@link ApplicationContext}. By default this
* method will respect any explicitly set application context or application context
* class before falling back to a suitable default.
* @return the application context (not yet refreshed)
* @see #setApplicationContextClass(Class)
*/
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext,"
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
5. 通过 prepareContext(context, environment, listeners, applicationArguments,printedBanner); 这行代码来准备容器:
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
<span class="hljs-comment">// Add boot specific singleton beans</span>
context.getBeanFactory().registerSingleton(<span class="hljs-string">"springApplicationArguments"</span>,
applicationArguments);
<span class="hljs-keyword">if</span> (printedBanner != <span class="hljs-literal">null</span>) {
context.getBeanFactory().registerSingleton(<span class="hljs-string">"springBootBanner"</span>, printedBanner);
}
<span class="hljs-comment">// Load the sources</span>
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, <span class="hljs-string">"Sources must not be empty"</span>);
load(context, sources.toArray(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>[<span class="hljs-number">0</span>]));
listeners.contextLoaded(context);
}
6. 通过 this.refreshContext(context); 刷新容器:
private void refreshContext(ConfigurableApplicationContext context) {
this.refresh(context);
if(this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
;
}
}
}
在刷新容器完成之后,调用 afterRefresh 函数,跟踪过去发现是空的,可能是预留了一个扩展点。
通过阅读发现 spring boot 2.0 的源码和 1.x 的源码对比起来,细节方面还是变化很大的。当然,这些还只是其中的冰山一角,后面我会继续与大家分享在源码阅读中的一些心得体会,