最后
码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到
又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考
以下是部分内容截图
@SpringBootApplication
public class AnnotationDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AnnotationDemoApplication.class, args);
}
}`
话不多说,DEBUG
伺候,别怕,搞它…
源码如何切分?
SpringApplication
中的静态run()
方法并不是一步完成的,最终执行的源码如下:
//org.springframework.context.ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
很显然分为两个步骤,分别是创建SpringApplication
和执行run()
方法,下面将分为这两个部分介绍。
如何创建SpringApplication?
创建即是new
对象了,DEBUG
跟进代码,最终执行的SpringApplication
构造方法如下图:
如上图中标注的注释,创建过程重用的其实分为②
、③
、④
这三个阶段,下面将会一一介绍每个阶段做了什么事。
设置应用类型
这个过程非常重要,直接决定了项目的类型,应用类型分为三种,都在WebApplicationType
这个枚举类中,如下:
NONE
:顾名思义,什么都没有,正常流程走,不额外的启动web容器
,比如Tomcat
。SERVLET
:基于servlet
的web程序,需要启动内嵌的servlet
web容器,比如Tomcat
。REACTIVE
:基于reactive
的web程序,需要启动内嵌reactive
web容器,作者不是很了解,不便多说。
判断的依据很简单,就是加载对应的类,比如加载了DispatcherServlet
等则会判断是Servlet
的web程序。源码如下:
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;
}
这里我引入了spring-boot-starter-web
,肯定是Servlet
的web程序。
设置初始化器(Initializer)
初始化器ApplicationContextInitializer
是个好东西,用于IOC
容器刷新之前初始化一些组件,比如ServletContextApplicationContextInitializer
。
那么如何获取初始化器呢?跟着上图中的代码进入,在SpringApplication
中的如下图中的方法:
相对重要的就是第一步获取初始化器的名称了,这个肯定是全类名
了,详细源码肯定在loadFactoryNames()
方法中了,跟着源码进入,最终调用的是#SpringFactoriesLoader.loadSpringFactories()
方法。
loadSpringFactories()
方法就不再详细解释了,其实就是从类路径META-INF/spring.factories
中加载ApplicationContextInitializer
的值。
在spring-boot-autoconfigure
的spring.factories
文件中的值如下图:
上图中的只是一部分初始化器,因为
spring.factories
文件不止一个。
下图中是我的demo
中注入的初始化器,现实项目中并不止这些。
这也告诉我们自定义一个
ApplicationContextInitializer
只需要实现接口,在spring.factories
文件中设置即可。
设置监听器(Listener)
监听器(ApplicationListener
)这个概念在Spring
中就已经存在,主要用于监听特定的事件(ApplicationEvent
),比如IOC容器刷新、容器关闭等。
Spring Boot
扩展了ApplicationEvent
构建了SpringApplicationEvent
这个抽象类,主要用于Spring Boot
启动过程中触发的事件,比如程序启动中、程序启动完成等。如下图:
监听器如何获取?从源码中知道其实和初始化器(ApplicationContextInitializer
)执行的是同一个方法,同样是从META-INF/spring.factories
文件中获取。
在spring-boot-autoconfigure
的spring.factories
文件中的值如下图:
spring.factories
文件不止一个,同样监听器也不止以上这些。
作者demo
中注入的一些监听器如下图:
总结
SpringApplication
的构建都是为了run()
方法启动做铺垫,构造方法中总共就有几行代码,最重要的部分就是设置应用类型、设置初始化器、设置监听器。
「注意」:初始化器和这里的监听器都要放置在
spring.factories
文件中才能在这一步骤加载,否则不会生效,因此此时IOC容器
还未创建,即使将其注入到IOC容器
中也是不会生效的。
作者简单的画了张执行流程图,仅供参考,如下:
执行run()方法
上面分析了SpringApplication
的构建过程,一切都做好了铺垫,现在到了启动的过程了。
作者根据源码将启动过程分为了**「8步」**,下面将会一一介绍。
1. 获取、启动运行过程监听器
SpringApplicationRunListener
这个监听器和ApplicationListener
不同,它是用来监听应用程序启动过程的,接口的各个方法含义如下:
public interface SpringApplicationRunListener {
// 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
void starting();
// 当environment构建完成,ApplicationContext创建之前,该方法被调用
void environmentPrepared(ConfigurableEnvironment environment);
// 当ApplicationContext构建完成时,该方法被调用
void contextPrepared(ConfigurableApplicationContext context);
// 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
void contextLoaded(ConfigurableApplicationContext context);
// 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
void started(ConfigurableApplicationContext context);
// 在run()方法执行完成前该方法被调用
void running(ConfigurableApplicationContext context);
// 当应用运行出错时该方法被调用
void failed(ConfigurableApplicationContext context, Throwable exception);
}
如何获取运行监听器?
在SpringApplication#run()
方法中,源码如下:
//从spring.factories中获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
跟进getRunListeners()
方法,其实还是调用了loadFactoryNames()
方法从spring.factories
文件中获取值,如下:
org.springframework.boot.SpringApplicationRunListener=
org.springframework.boot.context.event.EventPublishingRunListener
最终注入的是EventPublishingRunListener
这个实现类,创建实例过程肯定是通过反射了,因此我们看看它的构造方法,如下图:
这个运行监听器内部有一个事件广播器(SimpleApplicationEventMulticaster
),主要用来广播特定的事件(SpringApplicationEvent
)来触发特定的监听器ApplicationListener
。
EventPublishingRunListener
中的每个方法用来触发SpringApplicationEvent
中的不同子类。
如何启动运行监听器?
在SpringApplication#run()
方法中,源码如下:
//执行starting()方法
listeners.starting(bootstrapContext, this.mainApplicationClass);
执行SpringApplicationRunListeners
的starting()
方法,跟进去其实很简单,遍历执行上面获取的运行监听器,这里只有一个EventPublishingRunListener
。因此执行的是它的starting()
方法,源码如下图:
上述源码中逻辑很简单,其实只是执行了multicastEvent()
方法,广播了ApplicationStartingEvent
事件。至于multicastEvent()
内部方法感兴趣的可以看看,其实就是遍历ApplicationListener
的实现类,找到监听ApplicationStartingEvent
这个事件的监听器,执行onApplicationEvent()
方法。
总结
这一步其实就是广播了ApplicationStartingEvent
事件来触发监听这个事件的ApplicationListener
。
因此如果自定义了
ApplicationListener
并且监听了ApplicationStartingEvent
(应用程序开始启动)事件,则这个监听器将会被触发。
2. 环境构建
这一步主要用于加载系统配置以及用户的自定义配置(application.properties
),源码如下,在run()
方法中:
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
prepareEnvironment
方法内部广播了ApplicationEnvironmentPreparedEvent
事件,源码如下图:
环境构建这一步加载了系统环境配置、用户自定义配置并且广播了
ApplicationEnvironmentPreparedEvent
事件,触发监听器。
3. 创建IOC容器
源码在run()
方法中,如下:
context = createApplicationContext();
跟进代码,真正执行的是ApplicationContextFactory
方法,如下图:
根据webApplicationType
决定创建的类型,很显然,我这里的是servlet
,因此创建的是AnnotationConfigServletWebServerApplicationContext
。
这一步仅仅是创建了
IOC容器
,未有其他操作。
4. IOC容器的前置处理
这一步真是精华了,在刷新容器之前做准备,其中有一个非常关键的操作:将启动类注入容器,为后续的自动化配置奠定基础。源码如下:
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
prepareContext()
源码解析如下图,内容还是挺多的:
从上图可以看出步骤很多,下面将会详细介绍几个重点的内容。
调用初始化器
在SpringApplication
构建过程中设置的初始化器,从spring.factories
取值的。执行的流程很简单,遍历执行,源码如下图:
将自定义的
ApplicationContextInitializer
放在META-INF/spring.factories
中,在此时也是会被调用。
加载启动类,注入容器
这一步是将主启动类加载到IOC容器
中,作为后续自动配置的入口。
在SpringApplication
构建过程中将主启动类放置在primarySources
这个集合中,此时的getAllSources()
即是从其中取值,如下图:
这里取出的就是主启动类,当然你的项目中可能不止一个,接下来就是将其加载到IOC容器中了,源码如下:
load(context, sources.toArray(new Object[0]));
跟着代码进去,其实主要逻辑都在BeanDefinitionLoader.load()
方法,如下图:
将主启动类加载到
beanDefinitionMap
,后续该启动类将作为开启自动配置化的入口,后续章节详细介绍。
最后
现在正是金三银四的春招高潮,前阵子小编一直在搭建自己的网站,并整理了全套的**【一线互联网大厂Java核心面试题库+解析】:包括Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**
tty等等**
[外链图片转存中…(img-jbZdXdNI-1715060693167)]