写在前面
三年前,我在第一家公司的时候,还是用的原生SSM框架,用的是Double + zookeeper去实现“微服务”治理。 那时候发现市面上已经有很多开始用起了SpringBoot来搭建服务。第一印象,便是快捷、方便、不用外置tomcat,甩原生开发几条街(不由得想起了现在低代码时代)。
后来到现在的第二家公司,有幸用SpringBoot + SpringCloud治理微服务(所以后面我还会出SpringCloud相关的文章)。一用就是三年,有相当大的微服务治理心得和实战经验,扯的有点远了,话说回来,今天的主角SpringBoot
知其然,知其所以然,用了这么久,总得知道一些原理,前两天面了两个人,都答的不太好,今天我们来学习下。关于介绍SpringBoot的文章太多了,今天这里不多啰嗦了,应该没有人还不知道这是什么东东。我们今天主要是通过源码去了解启动背后的秘密
启动流程
为了故事的发展,我们先建一个SpringBoot服务
@SpringBootApplication
public class DemoApiApplication {
public static void main(String[] args) {
//run为启动入口
ApplicationContext applicationContext = SpringApplication.
run(DemoApiApplication.class, args);
}
}
复制代码
这行代码大家再了解不过了,更知道的是,这是启动入口,还有上面的SpringBootApplication注解,这个放到第二步讲,今天主要讲启动流程。
运行main函数,调用run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
//分为两步
// 1、new SpringApplication(primarySources)
// 2、run(args)
return new SpringApplication(primarySources).run(args);
}
复制代码
先看SpringApplication的构造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//将启动类放入primarySources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//根据classpath 下的类,推算当前web应用类型(webFlux, servlet)
//SERVLET : 该应用程序应作为基于 servlet 的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。
//REACTIVE : 该应用程序应作为响应式 Web 应用程序运行,并应启动嵌入式响应式 Web 服务器
this.webApplicationType = deduceWebApplicationType();
// 去spring.factories 中去获取所有key:org.springframework.context.ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//就是去spring.factories 中去获取所有key: org.springframework.context.ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 根据main方法推算出mainApplicationClass
this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码
上面两个set太抽象,什么意思呢,其实是利用SPI机制扫描 META-INF/spring.factories 这个文件,并且加载 ApplicationContextInitializer、ApplicationListener 接口实例。
扩展:什么是SPI,全称为 Service Provider Interface(服务提供者接口),是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件 夹查找文件,自动加载文件里所定义的类。
- ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
- ApplicationListener 当springboot启动时事件change后都会触发
有没有人疑问扫描收集这两个接口,目的是什么,看到第二步run的时候 大家就会明白,靠这些监听去处理事情
new SpringApplication总结
上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,利用SPI机制主要加载了META-INF/spring.factories 下面定义的事件***接口实现类
也可以分为几个步骤讲:
- 获取启动类
- 获取web应用类型
- 读取了对外扩展的ApplicationContextInitializer ,ApplicationListener
- 根据main推算出所在的类
核心run方法
上面第一步看构造方法发现并没有什么,那肯定核心的在第二步,我们看启动springboot最核心的逻辑run方法
public ConfigurableApplicationContext run(String... args) {
// 用来记录当前springboot启动耗时
StopWatch stopWatch = new StopWatch();
//记录了启动开始时间
stopWatch.start();
//关键类,它是任何spring上下文的接口, 所以可以接收任何ApplicationContext实现
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 去spring.factroies中读取了SpringApplicationRunListener 的组件, 就是用来发布事件或者运行***
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布1.ApplicationStartingEvent事件,在运行开始时发送
listeners.starting();
try {
// 根据命令行参数 实例化一个ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 很重要:::预初始化环境: 读取环境变量,读取配置文件信息(基于***)
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//忽略beaninfo的bean
configureIgnoreBeanInfo(environment);
// 打印Banner 横幅,就是我们的启动logo
Banner printedBanner = printBanner(environment);
//根据webApplicationType创建Spring上下文
//这个一会儿在下面再讲,主要就是内置tomcat
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//预初始化spring上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//加载spring ioc 容器相当重要 由于是使用AnnotationConfigServletWebServerApplicationContext 启动的sring容器所以springboot对它做了扩展:
// 加载自动配置类:invokeBeanFactoryPostProcessors , 创建servlet容器onRefresh
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
//部分代码省略
return context;
}
复制代码
上面的每行代码,每行解释,都要细细品一下,接下来我们把上面几个重要的点再深入下。预准备环境prepareEnvironment
预初始化环境prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//根据webApplicationType 创建Environment 创建就会读取: java环境变量和系统环境变量
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 将命令行参数读取环境变量中
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 发布了ApplicationEnvironmentPreparedEvent 的*** 读取了全局配置文件
listeners.environmentPrepared(environment);
//将所有spring.main 开头的配置信息绑定SpringApplication
bindToSpringApplication(environment);
//判断web容器,装载,说白了 就是tomcat类型,还记上上面讲的web类型吧
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
// 更新PropertySources
ConfigurationPropertySources.attach(environment);
return environment;
}
复制代码
上面我们看到发布了很多监听事件去处理很多事情,第一步的时候初始化监听的意义就是所在
创建上下文 createApplicationContext
这段代码主要是根据项目类型创建上下文,并且会注入几个核心组件类
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);
}
复制代码
预初始化上下文prepareContext
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 给上下文对象设置环境对象,给AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner设置环境对象
context.setEnvironment(environment);
// 1. 上下文后置处理,包括向BeanFactory中注册BeanNameGenerator和ConversionService
postProcessApplicationContext(context);
// 2. SpringApplication构造器中初始化了各种ApplicationContextInitializer,循环调用他们的initialize方法
applyInitializers(context);
// 3. 发送ApplicationContextInitializedEvent事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
// 打印启动信息,包括pid,用户等
logStartupInfo(context.getParent() == null);
// 答应profile信息
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 将ApplicationArguments注册到容器中
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
// 将Banner注册到容器中
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
// 设置不允许定义同名的BeanDefinition,重复注册时抛出异常
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
// 如果懒加载,添加懒加载后置处理器
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// 发送ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}
复制代码
最后一步 最最重要的就是调用Spring的启动代码加载了bean
refreshContext
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
//省略
复制代码
上面这个有没有很熟悉,就是Spring的初始化bean(启动容器),这里就不展开细讲。
说到这里 还有个小细节,那就是内置tomcat在哪,我们一起看下。
内置tomcat
上面说的Spring启动容器,这里主要看一下onRefresh() 这个方法。转到定义发现这个方法里面啥都没有,这明显是一个钩子方法,它会钩到它子类重写onRefresh()方法。所以去看子类里面的onRefresh()
protected void onRefresh() throws BeansException {
//这是一个空方法,AbstractApplicationContext 这个类是一个抽象类,
//所以我们要找到集成AbstractApplicationContext的子类,去看子类里面的onRefresh()
// For subclasses: do nothing by default.
}
复制代码
因为我们是web容器context,所以创建context的时候,createApplicationContext方法里有个ConfigurableApplicationContext接口 那么实现类即是ServletWebServerApplicationContext (上面有讲)
@Override
protected void onRefresh() {
super.onRefresh();
try {
//看到web容器了,我们进去看下
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
复制代码
上图中可以看到有jetty容器,tomcat容器,原来如此
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
复制代码
OK,今天的学习就到这里,最后我们先进行下简单总结
总结:
1. 初始化SpringApplication 从spring.factories 读取 listener ApplicationContextInitializer 。
2.运行run方法
3.读取 环境变量 配置信息.....
4. 创建springApplication上下文:ServletWebServerApplicationContext
5. 预初始化上下文 : 读取启动类
6.调用refresh 加载ioc容器
01 加载所有的自动配置类
02 创建servlet容器
复制代码
7.在这个过程中springboot会调用很多***对外进行
原文链接:https://juejin.cn/post/7091646469001707557