首先spring boot就是一个约定大于配置的简化开发的一个框架,如果看spring boot的源码,我觉得有两个方向,一个方向就是spring boot基于spring容器以及自身的一些启动的过程,另外一个就是约定大于配置这个目的实现代码。
从spring boot容器的启动过程开始。
spring boot的启动无非就是一行代码,SpringApplication.run,不过这是一个静态的方法,其中内部的调用如下。
/**
* 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);
}
当然建议直接使用new SpringApplication来启动spring boot,这样做有一个好处,可以在执行run方法前,设置一些自定义的属性。先来看看构造方法中的操作。
/**
* 创建一个新的SpringApplication实例,这个context会通过给定的primarySources参数加载bean
* 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 resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//resourceLoader 可以自己从外部传入,或者默认当前是空
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//WebApplicationType 有三种
// NONE 不需要内嵌的web容器,不以web的方式启动
// SERVLET 需要内嵌的servlet的web容器,启动一个基于servlet的web应用程序
// REACTIVE 需要内嵌的reactive的web容器,启动一个基于reactive的web应用程序 参考webflux
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从 META-INF/spring.factories 中找出实现了ApplicationContextInitializer的所有类设置到initializers中
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从 META-INF/spring.factories 中找出实现了ApplicationListener的所有类设置到listeners中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//找到当前main方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
具体的功能都写在注释里面了,deduceFromClasspath返回WebApplicationType方法重点看一看,代码如下,决定当前是什么WebApplicationType就通过查看Class.forName是否能加载到以下这几个类
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
SpringApplication初始化完成之后,执行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<>();
//配置headless,不用关注
configureHeadlessProperty();
//获取启动事件监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//传播启动事件
listeners.starting();
try {
//初始化一个应用程序参数类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//解析环境变量,参数,配置,发布事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
//创建spring容器类
context = createApplicationContext();
//获取继承了SpringBootExceptionReporter的类,异常输出
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器 spring refresh()
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
getRunListeners中获取SpringApplicationRunListener的实现类列表,而在spring boot的项目中默认有一个配置实现,EventPublishingRunListener
private SpringApplicationRunListeners getRunListeners(String[] args) {
//从 META-INF/spring.factories 中找出实现了SpringApplicationRunListener的所有类
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
EventPublishingRunListener对象构造方法初始化过程中,创建了一个SimpleApplicationEventMulticaster,并添加了当前所有的事件监听器。
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
在接下来的starting方法中,会调用EventPublishingRunListener监听器的starting方法,所以这里会向当前所有的监听器发布一个ApplicationStartingEvent事件
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
当前支持ApplicationStartingEvent事件的监听器一共有四个:
1、LoggingApplicationListener
2、BackgroundPreinitializer
3、DelegatingApplicationListener
4、LiquibaseServiceLocatorApplicationListener。
LoggingApplicationListener用于初始化日志的一些配置,
BackgroundPreinitializer用于初始化一些耗时的操作(这里部分方法也没赋值,也不是静态变量存储的,看了也挺懵逼的,也不知道干了啥),其他的没有操作。
接着会去初始化环境变量的配置参数,首先获取一个Environment,先看当前SpringApplication中存不存在Environment,默认肯定没有那么就会根据webApplicationType的类型新建返回不同的Environment。
获取到Environment之后,执行configureEnvironment方法,首先创建了一个ConversionService ,接着配置PropertySources以及Profiles
/**
* Template method delegating to
* {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
* {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
* Override this method for complete control over Environment customization, or one of
* the above for fine-grained control over property sources or profiles, respectively.
* @param environment this application's environment
* @param args arguments passed to the {@code run} method
* @see #configureProfiles(ConfigurableEnvironment, String[])
* @see #configurePropertySources(ConfigurableEnvironment, String[])
*/
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
// addConversionService默认true
if (this.addConversionService) {
//创建一个类型转换器服务实例,创建过程中已经注册了默认的所有的类型转换器,单例模式
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
//初始化PropertySources和Profiles
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
先看configurePropertySources的方法,首先看当前有没有defaultProperties,有的话添加,当前肯定没有,接下来就会判断当前有没有args变量,有的话创建一个SimpleCommandLinePropertySource。
接着是configureProfiles,这个方法就是将当前additionalProfiles变量中的Profiles和Environment中原有的组合,然后再放入Environment,当前additionalProfiles同样没有。
Environment装载完成之后,调用ConfigurationPropertySources.attach方法,创建一个ConfigurationPropertySourcesPropertySource放入Environment中。
接着执行environmentPrepared发布一个ApplicationEnvironmentPreparedEvent事件。
当前的默认事件接收者有以下几个:
1、ConfigFileApplicationListener
2、AnsiOutputApplicationListener
3、LoggingApplicationListener
4、BackgroundPreinitializer
5、ClasspathLoggingApplicationListener
6、DelegatingApplicationListener
7、FileEncodingApplicationListener
不过都对主线来说不是很重要,所以这里略过。
接着调用bindToSpringApplication,
createApplicationContext方法中开始创建spring的容器,根据不同的类型创建三种容器,当前看servlet的返回,AnnotationConfigServletWebServerApplicationContext
/**
* 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_SERVLET_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);
}
spring的启动流程可以参考Spring源码分析(一)IoC容器,先看类图,看到AnnotationConfigServletWebServerApplicationContext继承自GenericWebApplicationContext,在初始化上,这个容器类和AnnotationConfigApplicationContext流程一样
再往下prepareContext,首先向spring容器中设置了当前的Environment,接着执行postProcessApplicationContext方法,向spring容器中设置当前的resourceLoader 和beanNameGenerator ,不过当前也没有这两个,接着设置ConversionService
/**
* Apply any relevant post processing the {@link ApplicationContext}. Subclasses can
* apply additional processing as required.
* @param context the application context
*/
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
context.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());
}
}
if (this.addConversionService) {
context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
}
}
接着调用contextPrepared,发送ApplicationContextInitializedEvent事件,
这个事件接收者默认两个
1、BackgroundPreinitializer
2、DelegatingApplicationListener
接着向容器中注册单例ApplicationArguments,以及Banner,根据SpringApplication的lazyInitialization属性动态设置LazyInitializationBeanFactoryPostProcessor
接着执行load方法,这个可以参考spring容器的register方法,最后调用contextLoaded,这里先是找出继承了ApplicationContextAware的监听器,注入context容器对象,接着发布ApplicationPreparedEvent事件
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
当前事件接收者默认四个:
1、ConfigFileApplicationListener
2、LoggingApplicationListener
3、BackgroundPreinitializer
4、DelegatingApplicationListener
ConfigFileApplicationListener中添加了一个后置处理器PropertySourceOrderingPostProcessor,其他依旧略过。
接下来就会执行refreshContext方法,这个方法调用的就是spring容器的refresh方法,和AnnotationConfigApplicationContext那篇博客不同,当前是继承了GenericWebApplicationContext,所以部分子类实现部分有所区别。
其中先看prepareRefresh方法中的initPropertySources方法,这个方法是需要子类重写覆盖的,GenericWebApplicationContext中重写了该方法,不过由于当前这个servletContext不存在,所以方法也不会调用
/**
* {@inheritDoc}
* <p>Replace {@code Servlet}-related property sources.
*/
@Override
protected void initPropertySources() {
ConfigurableEnvironment env = getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null);
}
}
接着是postProcessBeanFactory,AnnotationConfigServletWebServerApplicationContext中重写了这个方法,向容器中注册了一个WebApplicationContextServletContextAwareProcessor。
最重要的是onRefresh方法的重写,ServletWebServerApplicationContext中将会创建服务器。
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
首先当前webServer和servletContext默认肯定是空,所以会从spring容器中获取ServletWebServerFactory的首个Bean,当前是TomcatServletWebServerFactory
/**
* Returns the {@link ServletWebServerFactory} that should be used to create the
* embedded {@link WebServer}. By default this method searches for a suitable bean in
* the context itself.
* @return a {@link ServletWebServerFactory} (never {@code null})
*/
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
接下来会启动tomcat,这个过程略过,但是需要注意的是这里注册了一个回调,tomcat启动后会调用,以下代码是jdk8的方法引用。
/**
* Returns the {@link ServletContextInitializer} that will be used to complete the
* setup of this {@link WebApplicationContext}.
* @return the self initializer
* @see #prepareWebApplicationContext(ServletContext)
*/
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
调用如下,首先prepareWebApplicationContext内容很简单,就是打印了几行日志,将servletContext设置到当前容器中,registerApplicationScope则是将servletContext设置为application的scope,registerEnvironmentBeans将contextParameters和contextAttributes注册进容器中,然后再onStartup方法中添加了一些默认的过滤器什么的。
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
最后再调用一遍initPropertySources方法。spring容器启动完成之后,返回,注册一个回调钩子,这个回调钩子会在jvm停止时被调用
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
接着调用afterRefresh方法,当前没有实现
接着调用listeners.started,发布ApplicationStartedEvent事件
当前事件接收者默认两个:
1、BackgroundPreinitializer
2、DelegatingApplicationListener
接着调用callRunners方法,这个方法就是找出实现了ApplicationRunner以及CommandLineRunner,调用run方法
最后调用listeners.running,发布ApplicationReadyEvent事件
当前事件接收者默认三个:
1、SpringApplicationAdminMXBeanRegistrar
2、BackgroundPreinitializer
3、DelegatingApplicationListener
spring boot启动完成。