关注“Java 后端技术全栈”**
回复“面试”获取全套面试材料
在后面文章中,咱们聊过 SpringBoot 是如何解决依赖配置,以及如何实现主动拆卸的。明天咱们持续来聊 Springboot 的启动流程。
SpringBoot 我的项目是如何启动的?
@SpringBootApplication
public class BlogApplication {public static void main(String[] args) {SpringApplication.run(BlogApplication.class, args);
}
}
这就是咱们我的项目中所谓的启动类。上一篇文章【原创】Spring Boot 终极篇《上》咱们次要是聊了注解 @SpringBootApplication
原理以及是如何实现主动拆卸的。在这里启动类里还有很重要就是 main 办法里的:
SpringApplication.run(BlogApplication.class, args);
无妨咱们进去看看这个 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);
}
从下面的源码中,咱们能够看到这个 run 办法,实际上分两步:
- 创立一个 SpringApplication 实体
- 执行 run 办法。
SpringApplication 实例化
咱们点进去看下,发现其实就是一个初始化的过程。
说一下几个绝对重要的配置:
// 我的项目启动类 SpringbootDemoApplication.class 设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 设置利用类型是 SERVLET 利用(Spring 5 之前的传统 MVC 利用)还是 REACTIVE 利用(Spring 5 开始呈现的 WebFlux 交互式利用)this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置初始化器(Initializer), 最初会调用这些初始化器
// 所谓的初始化器就是 org.springframework.context.ApplicationContextInitializer 的实现类, 在 Spring 上下文被刷新之前进行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 初始化 mainApplicationClass 属性: 用于推断并设置我的项目 main()办法启动的主程序启动类
this.mainApplicationClass = deduceMainApplicationClass();
run 办法初始化启动
咱们接下来看下 run 办法。这个 run 办法执行的步骤比拟多。上面是源码及局部正文:
public ConfigurableApplicationContext run(String... args) {
// 创立 StopWatch 对象,并启动。StopWatch 次要用于简略统计 run 启动过程的时长。StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 初始化利用上下文和异样报告汇合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 headless 属性
configureHeadlessProperty();
//(1)获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 创立 ApplicationArguments 对象 初始化默认利用参数类
// args 是启动 Spring 利用的命令行参数,该参数能够在 Spring 利用中被拜访。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//(2)我的项目运行环境 Environment 的预配置
// 创立并配置以后 SpringBoot 利用将要应用的 Environment
// 并遍历调用所有的 SpringApplicationRunListener 的 environmentPrepared()办法
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 筹备 Banner 打印器 - 就是启动 Spring Boot 的时候打印在 console 上的 ASCII 艺术字体
Banner printedBanner = printBanner(environment);
//(3)创立 Spring 容器
context = createApplicationContext();
// 取得异样报告器 SpringBootExceptionReporter 数组
// 这一步的逻辑和实例化初始化器和监听器的一样,// 都是通过调用 getSpringFactoriesInstances 办法来获取配置的异样类名称并实例化所有的异样解决类。exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class}, context);
//(4)Spring 容器前置解决
// 这一步次要是在容器刷新之前的筹备动作。蕴含一个十分要害的操作:将启动类注入容器,为后续开启自动化配置奠定根底。prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//(5):刷新容器
refreshContext(context);
//(6):Spring 容器后置解决
// 扩大接口,设计模式中的模板办法,默认为空实现。// 如果有自定义需要,能够重写该办法。比方打印一些启动完结 log,或者一些其它后置解决
afterRefresh(context, applicationArguments);
// 进行 StopWatch 统计时长
stopWatch.stop();
// 打印 Spring Boot 启动的时长日志。if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//(7)收回完结执行的事件告诉
listeners.started(context);
//(8):执行 Runners
// 用于调用我的项目中自定义的执行器 XxxRunner 类,使得在我的项目启动实现后立刻执行一些特定程序
//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。//Spring Boot 提供了 ApplicationRunner 和 CommandLineRunner 两种服务接口
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 如果产生异样,则进行解决,并抛出 IllegalStateException 异样
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
// (9)公布利用上下文就绪事件
// 示意在后面所有初始化启动都没有问题的状况下,应用运行监听器 SpringApplicationRunListener 继续运行配置好的利用上下文 ApplicationContext,// 这样整个 Spring Boot 我的项目就正式启动实现了。try {listeners.running(context);
} catch (Throwable ex) {
// 如果产生异样,则进行解决,并抛出 IllegalStateException 异样
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
// 返回容器
return context;
}
这里咱们能够将次要执行流程列出来:
1、获取并启动监听器
2、依据监听器和参数来创立运行环境
3、筹备 Banner 打印器
4、创立 Spring 容器
5、Spring 容器前置解决
6、刷新容器
7、Spring 容器后置解决
8、收回完结执行的事件告诉
9、返回容器
在 run 办法中,在最开始的时候,会启动一个 StopWatch 对象,用来记录 run 办法执行时长,这个咱们理解就行,而后是一些初始化,接下啦外围一步就是创立并启动监听器啦。创立好监听器后,就须要创立运行环境。prepareEnvironment () . 次要初始化加载内部化配置资源到 environment,包含命令行参数、servletConfigInitParams、application.yml(.yaml/.xml/.properties) 等;初始化日志零碎。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {ConfigurableEnvironment environment = this.getOrCreateEnvironment();
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared((ConfigurableEnvironment)environment);
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
接下来打印 banner
// 筹备 Banner 打印器 - 就是启动 Spring Boot 的时候打印在 console 上的 ASCII 艺术字体
Banner printedBanner = printBanner(environment);
再接下来创立 spring 容器
context = createApplicationContext();
// 取得异样报告器 SpringBootExceptionReporter 数组
// 这一步的逻辑和实例化初始化器和监听器的一样,// 都是通过调用 getSpringFactoriesInstances 办法来获取配置的异样类名称并实例化所有的异样解决类。exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class}, context);
创立好 spring 容器后,开始 spring 容器的前置解决
/ 这一步次要是在容器刷新之前的筹备动作。蕴含一个十分要害的操作:将启动类注入容器,为后续开启自动化配置奠定根底。prepareContext(context, environment, listeners, applicationArguments, printedBanner);
前置解决做了很多操作:次要是将启动类注入到容器中,蕴含环境,上下文,bean 等等。
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置容器环境,包含各种变量
context.setEnvironment(environment);
// 设置上下文的 bean 生成器和资源加载器
postProcessApplicationContext(context);
// 执行容器中的 ApplicationContextInitializer(包含 spring.factories 和自定义的实例)applyInitializers(context);
// 触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件办法
listeners.contextPrepared(context);
// 记录启动日志
if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 注册启动参数 bean,这里将容器指定的参数封装成 bean,注入容器
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
// 加载所有资源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加载咱们的启动类,将启动类注入容器, 为后续开启自动化配置奠定根底
load(context, sources.toArray(new Object[0]));
// 触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件办法
listeners.contextLoaded(context);
// 这块会对整个上下文进行一个预处理,比方触发监听器的响应事件、加载资源、设置上下文环境等等
}
前置解决 执行完之后,就会刷新容器
private void refreshContext(ConfigurableApplicationContext context) {this.refresh(context);
if (this.registerShutdownHook) {
try {context.registerShutdownHook();
} catch (AccessControlException var3) {;}
}
}
次要是对 IOC 容器的初始化。而后就是 spring 容器的后置解决。
afterRefresh(context, applicationArguments);
咱们能够重写这个办法,实现咱们自定义的需要。
接下来就是收回完结执行的事件告诉:
listeners.started(context);
public void started(ConfigurableApplicationContext context) {context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}
最初就是执行 Runners:
callRunners(context, applicationArguments);
private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
Iterator var4 = (new LinkedHashSet(runners)).iterator();
while(var4.hasNext()) {Object runner = var4.next();
if (runner instanceof ApplicationRunner) {this.callRunner((ApplicationRunner)runner, args);
}
if (runner instanceof CommandLineRunner) {this.callRunner((CommandLineRunner)runner, args);
}
}
}
用于调用我的项目中自定义的执行器 XxxRunner 类,使得在我的项目启动实现后立刻执行一些特定程序。Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。Spring Boot 提供了 ApplicationRunner 和 CommandLineRunner 两种服务接口。
执行完之后,就是返回上下文容器了。
启动流程的总结
以上便是 Spring Boot 的整个启动流程。好啦,最初来总结一下,整个 Spring Boot 的启动流程,分为以下几个步骤:
- 加载并且启动监听器
- 创立我的项目运行环境,加载配置
- 初始化 Spring 容器
- 执行 Spring 容器前置处理器
- 刷新 Spring 容器
- 执行 Spring 后置处理器
- 公布事件
- 执行自定义执行器
- 返回容器
看源码时候,咱们尽量看重点,重视整体设计思路的把握。
举荐浏览
口试题:代码如何实现“百钱买百鸡”?
【原创】90% 的人都不会做的一道口试题
《Redis 入门指南》.pdf