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&lt;?&gt;... 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>&lt;&gt;(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&lt;Object&gt; 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 的源码对比起来,细节方面还是变化很大的。当然,这些还只是其中的冰山一角,后面我会继续与大家分享在源码阅读中的一些心得体会,