环境:
- spring boot 2.0.8.release
- win10
- idea 2018.03.05
本文查看相关的文章与源代码总结的。
1. spring boot 启动流程
查看 SpringBoot启动流程解析 这篇文章,那里的启动结构图还是不错的。
启动流程主要分为三部分,
- 第一部分, 进行SpringApplication的初始化模块,配置一些环境变量、资源、构造器、监听器、主方法的类;
- 第二部分,实现应用的启动,包括启动流程的监听模块、加载配置环境模块、核心的创建上下文环境模块;
- 第三部分,自动化配置模块,该模块作为spring boot的自动配置核心。
查看一下的图,这个图比较精彩。
如果不够全面,就查看[SpringBoot启动结构图] (https://www.processon.com/view/link/59812124e4b0de2518b32b6e)
2. 启动
每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类需要使用@SpringBootApplication注解,以及@ImportResource注解(if need),@SpringBootApplication包括三个注解,功能如下:
@EnableAutoConfiguration:SpringBoot根据应用所声明的依赖来对Spring框架进行自动配置
@SpringBootConfiguration(内部为@Configuration):被标注的类等于在spring的XML配置文件中(applicationContext.xml),装配所有bean事务,提供了一个spring的上下文环境
@ComponentScan:组件扫描,可自动发现和装配Bean,默认扫描SpringApplication的run方法里的Demo.class所在的包路径下文件,所以最好将该启动类放到根包路径下
3. 初始化模块
SpringBoot启动类进入run()方法,首先会创建SpringApplication实例,然后在构造函数内,初始化initialize.
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);
}
------------------------------------------
public SpringApplication(Class>... primarySources) {
this(null, primarySources);
}
-------------------------------------------
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
// 配置类加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 配置sources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判断应用的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 创建初始化构造器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));、
// 配置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 配置应用主类
this.mainApplicationClass = deduceMainApplicationClass();
}
其中WebApplicationType.deduceFromClasspath();主要配置是否为Web环境, REACTIVE环境,None(单jar)。
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;
}
注意: classUtils.isPresent(className, null)判断所提供的类名的类是否存在,且可以被加载
java.lang.Class.forName()的作用是什么?
返回与给定字符串名的类或接口的Class对象,使用给定的类加载器。
private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
4. 应用的启动
应用的启动,会启动监听器、配置环境模块、配置Banner、配置应用上下文模块
public ConfigurableApplicationContext run(String... args) {
// 开启计时器开始计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection 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
Banner printedBanner = printBanner(environment);
// 创建应用上下文
context = createApplicationContext();
// 获取springboot 异常类
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 更新应用上下文,prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// (初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。
refreshContext(context);
// Springboot做了一些基本的收尾工作,返回了应用环境上下文
afterRefresh(context, applicationArguments);
// 停止计时
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 这里会检测是否Runner,在项目启动后运行的,一般是CommandRunner、ApplicationRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
该方法中实现了如下几个关键步骤:
-
- 创建了应用的监听器SpringApplicationRunListeners并开始监听
-
- 加载SpringBoot配置环境(ConfigurableEnvironment),如果是通过web容器发布,会加载StandardEnvironment,其最终也是继承了ConfigurableEnvironment,类图如下
可以看出,*Environment最终都实现了PropertyResolver接口,我们平时通过environment对象获取配置文件中指定Key对应的value方法时,就是调用了propertyResolver接口的getProperty方法
-
- 配置环境(Environment)加入到监听器对象中(SpringApplicationRunListeners)
-
- 创建run方法的返回对象:ConfigurableApplicationContext(应用配置上下文),我们可以看一下创建方法:
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);
}
方法会先获取显式设置的应用上下文(applicationContextClass),如果不存在,再加载默认的环境配置(通过是否是web environment判断),默认选择AnnotationConfigApplicationContext注解上下文(通过扫描所有注解类来加载bean),最后通过BeanUtils实例化上下文对象,并返回,ConfigurableApplicationContext类图如下:
主要看其继承的两个方向:
LifeCycle:生命周期类,定义了start启动、stop结束、isRunning是否运行中等生命周期空值方法
ApplicationContext:应用上下文类,其主要继承了beanFactory(bean的工厂类)
5.回到run方法内,prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联
6.接下来的refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
// 注册关闭的钩子函数,用于收尾工作
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
-----------------------
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
-----------------------
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
// 刷新,检测环境变量
this.prepareRefresh();
// 获取ConfigurableListableBeanFactory工厂
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
//
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
- refresh方法
配置结束后,Springboot做了一些基本的收尾工作,返回了应用环境上下文。
回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean,至此,通过SpringBoot启动的程序已经构造完成,接下来我们来探讨自动化配置是如何实现。
引用:
- https://www.cnblogs.com/trgl/p/7353782.html
- https://blog.csdn.net/doegoo/article/details/52471310
PS: 若你觉得可以、还行、过得去、甚至不太差的话,可以“关注”一下,就此谢过!