一个简单的SB程序如下,点击main方法左边的原谅色的三角形就能把程序启动起来,虽然什么功能都没有,但是启动做了很多处理,加载了很多支持多种功能的组件(类似使用new ClassPathXmlApplicationContext()
启动一个Spring程序,其中加载了很多东西)
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
分析的入口就是从SpringApplication的run方法开始,而@SpringBootApplication
注解在后续的处理中会用到,所以暂时忽略
1.入口
进入run方法
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
2.初始化SpringApplication
首先初始化了SpringApplication
,然后再调用其run方法,那么先看下SpringApplication的构造方法里的逻辑
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 即demo里的SpringBootDemoApplication对应的Class对象集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 应用的类型,共三种类型
// reactive、servlet 、非web应用
this.webApplicationType = deduceWebApplicationType();
// 获取spring.factories中配置的ApplicationContextInitializer的实现类并实例化,最后放到集合当中
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 原理同上
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 找到执行main方法的那个类的Class对象,即demo中的SpringBootDemoApplication对应的Class对象
this.mainApplicationClass = deduceMainApplicationClass();
}
2.1获取应用类型
在SpringApplication
构造方法里,调用了deduceWebApplicationType
来判断是什么类型的应用,那么SpringBoot是如何判断的呢?往里进去看下具体实现
private WebApplicationType deduceWebApplicationType() {
//REACTIVE_WEB_ENVIRONMENT_CLASS = org.springframework.web.reactive.DispatcherHandler
//MVC_WEB_ENVIRONMENT_CLASS = org.springframework.web.servlet.DispatcherServlet
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
//WEB_ENVIRONMENT_CLASSES=javax.servlet.Servlet
// 或者org.springframework.web.context.ConfigurableWebApplicationContext
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
ClassUtils.isPresent
方法中尝试加载传入的类,如果加载成功,则返回true,如果失败则返回false,SpringBoot使用这种方式在判断当前是什么类型的应用。
从假设我们应用中什么依赖都没有加入,那么WEB_ENVIRONMENT_CLASSES
、REACTIVE_WEB_ENVIRONMENT_CLASS
或者MVC_WEB_ENVIRONMENT_CLASS
都是加载失败的,最终返回WebApplicationType.NONE
。
从另一个方面来说,如果我们想构建一个SpringMVC的web应用,那么只需要加入如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
此时,SpringBoot会帮我们间接依赖SpringMVC所需要的包,那么就间接引入了org.springframework.web.context.ConfigurableWebApplicationContext
这个包,由于SpringMVC底层是基于Servlet,那么会引入javax.servlet.Servlet
相关的包。加了上面这一个依赖之后,deduceWebApplicationType方法就会返回WebApplicationType.SERVLET。
后续SpringBoot会根据deduceWebApplicationType
的返回类型,来做不同的初始化,这也算是SpringBoot自动化的其中一个体现。
2.2 Spring的扩展机制
在构造方法中,会初始化initializers和listeners两个几个,里面的对应的类型分别是ApplicationContextInitializer
和ApplicationListener
类型的实例。
- ApplicationContextInitializer:用于上下文刷新前(refreshContext)的回调处理
- ApplicationListener:用于事件的通知
这两个集合都是通过getSpringFactoriesInstances
方法去加载的,而核心的实现就是Spring的扩展机制。
SpringBoot会基于这个机制,会提供一些系统内置的类(分别实现ApplicationContextInitializer和ApplicationListener),让系统自动的调用对应的实现,而这些内置类的加载则是通过getSpringFactoriesInstances方法实现的:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//获取类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 获取对应的类名
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
SpringFactoriesLoader
就是扩展机制的核心实现,其大概逻辑就是,查找META-INF文件下的spring.factories文件,通过对应类型获取配置好的类名,以ApplicationContextInitializer为例,引入的jar包中,其下面的spring.factories文件如下:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
故getSpringFactoriesInstances(ApplicationContextInitializer.class)
得到的是的是上面6个类的实例对象集合,ApplicationListener就不再分析
基于此,我们也可以在工程的对应路径下加入spring.factories文件,并用上面的规则,加入自己的实现来扩展对应的功能
3.SpringApplication.run方法
当SpringApplication构造完成,就进入到run方法,进行真正的初始化步骤
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//设置awt相关属性
configureHeadlessProperty();
// 通过扩展机制获取SpringApplicationRunListener的实现类,并封装成SpringApplicationRunListeners对象
// 目前只有一个实现EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布ApplicationStartingEvent事件
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//环境准备
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//设置需要忽略的Bean信息
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
//根据应用类型创建对应的ApplicationContext
context = createApplicationContext();
//获取错误报告处理器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//刷新上下文前的准备工作
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新上下文,即Spring的核心流程,IOC,AOP,Bean初始化等流程
refreshContext(context);
//刷新完成后置处理
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//发布ApplicationStartedEvent事件
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//....
}
//发布ApplicationReadyEvent事件
listeners.running(context);
return context;
}
下面对核心步骤进行分析
3.1 环境准备
环境主要指的是Environment
,Environment主要包括两部分功能:
- profile
- properties
其中profile是Environment自身的功能,而properties相关功能则是由Environment
继承的接口PropertyResolver
提供
prepareEnvironment
方法会构建一个ConfigurableEnvironment
实例,并对其进行初始化
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 根据应用类型返回不同的实例
ConfigurableEnvironment environment = getOrCreateEnvironment();
// properties和profile相关处理
configureEnvironment(environment, applicationArguments.getSourceArgs());
//发布ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
先看下getOrCreateEnvironment方法
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
if (this.webApplicationType == WebApplicationType.SERVLET) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
由于是Servlet的web应用,则返回StandardServletEnvironment
实例,其他情况则返回StandardEnvironment
(StandardEnvironment是StandardServletEnvironment的父类)。这里为什么Servlet应用需要特殊处理呢?
前面说过Environment包括properties和profile,我们知道Servlet的properties来源和其他类型的应用相比,还有ServletContext
里的参数,那么Servlet应用需要另外的解析工作(基础解析工作由StandardEnvironment完成),而这个工作需要由StandardServletEnvironment
特殊处理。
在其父类AbstractEnvironment
的构造方法中,会调用到customizePropertySources
方法,这个方法由子类重写来自定义解析策略(实际是加入处理的PropertySource
),StandardServletEnvironment
的customizePropertySources
方法如下:
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
这里看到该方法中加入两个PropertySource
用于Servlet特有的属性解析,然后再调用父类StandardEnvironment
的方法保证基础的属性解析也没有问题,看下StandardEnvironment
的方法
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
这里加入了两个PropertySource用于解析其他属性,即System.getProperties()和System.getEnv()两个方法所附带的属性。
- 至于PropertySource所涉及的属性解析原理,这里不再展开,后续会另起文章详细分析属性解析原理。
环境Environment的准备中,主要是为属性解析和profile做了准备工作,例如添加了PropertySource待后续参数获取使用
3.2 创建对应的ApplicationContext
创建上下文主要交由createApplicationContext
处理
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
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);
}
逻辑比较简单,根据应用类型创建不同的上下文对象
3.3 刷新上下文前的准备工作
该工作交由prepareContext处理:
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
// 后置处理,注册BeanNameGenerator和设置ResourceLoader
postProcessApplicationContext(context);
// 遍历initializers集合内的ApplicationContextInitializer实例进行调用
applyInitializers(context);
// 发布事件,目前看到的实现为空
listeners.contextPrepared(context);
// 将之前封装的参数对象注册到容器中
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// 即上文提到的primarySources
Set<Object> sources = getAllSources();
// 将我们的启动类注册到容器中
load(context, sources.toArray(new Object[0]));
//发布ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}
3.3.1 postProcessApplicationContext
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());
}
}
}
代码逻辑很简单,但是该方法中beanNameGenerator
和resourceLoader
都为空,所以这个地方是Springboot留的一个扩展点,可以将自定义的设置进去
3.3.2 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);
}
}
在前面的分析中,Spring会从spring.factories中获取ApplicationContextInitializer的实现并加入到集合当中,而这里就是真正调用的地方,具体的每个实现后续在相关功能中进行分析
3.3.3 load
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
将启动类封装成一个BeanDefinitionLoader
,然后设置beanNameGenerator、resourceLoader、environment,最后调用load方法,将启动类注册到容器当中。
主要是创建了一个AnnotatedGenericBeanDefinition
,然后使用BeanDefinitionRegistry,以beanName为key,将其注册到容器中。
这里涉及了Spring的知识,就不再详细展开。
3.4 刷新上下文
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
refresh为Spring的核心流程,即org.springframework.context.support.AbstractApplicationContext#refresh
,进行bean的创建等等一些核心流程。
另外会注册一个核心关闭钩子。