简单概括一下Springboot的源码流程架构,一些比较重要的类和细节会详细写出来。
Springboot的启动流程
我们知道,Springboot就是靠这串SpringApplication的静态方法便启动了web服务:
SpringApplication.run(DemoApplication.class, args);
它的实现是封装了一个SpringApplication对象,并调用run方法的重载:new SpringApplication(primarySources)).run(args)
;
这个run方法的核心代码是干了这样几件事:
public ConfigurableApplicationContext run(String... args) {
// 计时工具
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 第一步:获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 第二步:根据SpringApplicationRunListeners以及参数来准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);
// 第三步:创建Spring容器
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 第四步:Spring容器前置处理
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
// 第五步:刷新容器
refreshContext(context);
// 第六步:Spring容器后置处理
afterRefresh(context, applicationArguments);
// 第七步:发出结束执行的事件
listeners.started(context);
// 第八步:执行Runners
this.callRunners(context, applicationArguments);
stopWatch.stop();
// 返回容器
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
}
这里过一下其中的核心步骤。
获取并启动监听器(SpringApplicationRunListener)
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
Springboot启动中涉及到的事件机制组件
- 事件源 –
SpringApplication
- 事件 –
ApplicationEvent
SpringApplication的生命周期。
事件名 | 作用 |
---|---|
ApplicationStartingEvent | 框架启动事件 |
ApplicationEnvironmentPreparedEvent | 环境准备完毕事件 |
ApplicationContextInitializedEvent | 上下文初始化 |
ApplicationPreparedEvent | 上下文创建完毕,但是Bean还没有加载完毕 |
ApplicationStartedEvent | bean 实例化完成,但是未调用 Runners接口 |
ApplicationReadyEvent | 调用 Runners 接口完毕 |
ApplicationFailedEvent | 启动失败事件 |
- 事件发布者 –
ApplicationEventPublisher
及ApplicationEventMulticaster
- 监听器 –
java.util.EventListener
子类接口ApplicationListener
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
return (event) -> {
consumer.accept(event.getPayload());
};
}
}
实际上Springboot中用到的Listener默认只有EventPublishingRunListener
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
//广播器
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
Iterator var3 = application.getListeners().iterator();
while(var3.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var3.next();
//将上面设置到SpringApplication的十一个监听器全部添加到SimpleApplicationEventMulticaster这个广播器中
this.initialMulticaster.addApplicationListener(listener);
}
}
//略...
}
获取SpringApplicationRunListener
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
}
getRunListeners返回的是一个SpringApplicationRunListeners
,就是对获取到监听器的包装。
这里想要获取的监听器是SpringApplicationRunListener.class类型的监听器,看得出来就是对SpringApplication整个运行过程中的监听器。
这里调用了SpringApplication#getSpringFactoriesInstances
,最后获取的就是一个EventPublishingRunListener
这个是Springboot获取bean的一个重要方法:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
这一步,就是我们熟知的,从META-INF/spring.factories读取bean的全限定类名;结束后再传给下面的方法反射实例化。
这里就是从META-INF/spring.factories中读取Key为org.springframework.boot.SpringApplicationRunListener的Values:
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
所以获取的SpringApplicationRunListeners
中只有这个EventPublishingRunListener
,当然你也可以自己扩展,并在META-INF/spring.factories
中自己写入。
这里又出现了一个问题:我们这个如以maven构建的项目目录中并没有显式出现META-INF这个目录,哪这个META-INF/spring.factories
及其里面的内容是哪来的呢?
其实,在pom.xml在引入的
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
依赖组中,包含了一项名为spring-boot-autoconfigure-x.x.x
的依赖,
这个jar包中就包含了Springboot启动所需的META-INF/spring.factories
等一系列配置,当然,并不会与项目目录中手动建立的META-INF/spring.factories
冲突,两个文件会合并解析。
启动SpringApplicationRunListener
要了解一个逻辑复杂的类就先去了解它的接口:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
...
}
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);
}
先来看看SpringBoot启动时第一个启动事件listeners.starting():
@Override
public void starting() {
//关键代码,先创建application启动事件`ApplicationStartingEvent`
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
调用封装好的SimpleApplicationEventMulticaster
对象的multicastEvent,准备利用广播器发布事件并又启用监听器。其大部分对事件的处理都是调用Multicaster,可见得这个监听器的职责更像是作为Multicaster
的补充。
跟进SimpleApplicationEventMulticaster:
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//通过事件类型ApplicationStartingEvent获取对应的监听器
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
//获取线程池,如果为空则同步处理。这里线程池为空,还未没初始化。
Executor executor = getTaskExecutor();
if (executor != null) {
//异步发送事件
executor.execute(() -> invokeListener(listener, event));
}
else {
//同步发送事件
invokeListener(listener, event);
}
}
}
这个getApplicationListeners
方法会把入参事件的.class取出来,并看哪些监听器(监听器也是从META-INF/spring.factories
中取出实例化)对这个事件event和事件源source”感兴趣”(在每个Listener中定义了列表,对应具体事件、事件源类,传入的事件在这列表里面就表示监听器对其”感兴趣”)并将所有对该事件”感兴趣”的监听器返回。
这里就有如下4种监听器:
以日志监听器:LoggingApplicationListener为例:
这就是提到的支持的source和type:
这是其最后调用的方法:
public void onApplicationEvent(ApplicationEvent event) {
//在springboot启动的时候
if (event instanceof ApplicationStartedEvent) {
onApplicationStartedEvent((ApplicationStartedEvent) event);
}
//springboot的Environment环境准备完成的时候
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
//在springboot容器的环境设置完成以后
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
//容器关闭的时候
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
//容器启动失败的时候
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
这个类就是在springboot启动时日志输出的实现类。
至此
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
这两串就介绍完了。这个EventPublishingRunListener会贯穿在SpringApplication整个运行过程。
环境准备
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
SpringApplication:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//获取对应的ConfigurableEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
//发布环境已准备事件,这是第二次发布事件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
ConfigurationPropertySources.attach(environment);
return environment;
}
可以看到还是利用EventPublishingRunListener去发布事件,那么就继续跟进到其内部的SimpleApplicationEventMulticaster#multicast
,会调用到的监听器:
其中,重点来看ConfigFileApplicationListener
,该监听器非常核心,主要用来处理项目配置。项目中的 properties 和yml文件都是其内部类所加载。
先从META-INF/spring.factories
中读取几个Processors,再执行其postProcessEnvironment,最后执行该监听器本身的逻辑,即加载其定义路径下的配置文件:
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
可以看到,在发布事件处理结束后,environment变量存放了我们在resources\application.properties
配置的参数:
应用上下文的初始化、后处理
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
afterRefresh(context, applicationArguments);
这里的ApplicationContext也常被成为Spring容器,它是基于 Spring 框架定义的 IoC 容器,是负责 Bean 生命周期管理的核心组件。
1.createApplicationContext
先根据容器类型判断,此处是SERVLET
类型,所以会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext。
2.prepareContext(初始化1)
然后prepareContext开始配置容器
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置容器环境,包括各种变量
context.setEnvironment(environment);
//执行容器后置处理
postProcessApplicationContext(context);
//执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
applyInitializers(context);
//发送容器已经准备好的事件,通知各监听器
listeners.contextPrepared(context);
//注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
//设置banner
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
//获取我们的启动类指定的参数,可以是多个
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载我们的启动类,将启动类注入容器
load(context, sources.toArray(new Object[0]));
//发布容器已加载事件。
listeners.contextLoaded(context);
}
调用postProcess、循环调用ApplicationContextInitializer中的initialize方法、将启动类注入容器、发布容器已加载(打印日志)。
3.refresh(初始化2)
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 启动步骤标记
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 准备刷新 Prepare this context for refreshing.
prepareRefresh();
// 通知子类刷新内部bean工厂 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.
// Bean工厂后置处理
postProcessBeanFactory(beanFactory);
// 步骤标记
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
// 执行Bean工厂后置处理 核心大哥有个是ConfigurationClassPostProcessor @Configuration @ComponentScan都是这大哥
invokeBeanFactoryPostProcessors(beanFactory);
// 注册Bean 后置处理器 Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 国际化 Initialize message source for this context.
initMessageSource();
// 注册事件发布器 Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
// 这里对于SpringBoot主要就是创建WebServer
onRefresh();
// 注册监听器 Check for listener beans and register them.
registerListeners();
// 初始化非延迟加载Bean Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// 完成刷新 Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁Bean Destroy already created singletons to avoid dangling resources.
destroyBeans();
// 取消刷新 Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
// 上下文刷新结束
contextRefresh.end();
}
}
}
这段代码是spring架构中的核心,是实现ioc和aop的关键;负责刷新应用程序上下文,这里主要涉及到准备刷新上下文,调用上下文注册为 bean 的工厂处理器,初始化上下文的消息源,初始化特定上下文子类中的其他特殊 bean,检查监听器 bean 并注册,最后发布相应的事件并销毁已经创建的单例及重置 active 标志。
大概做了这些事:
- 准备刷新(
prepareRefresh()
)—— 初始化环境变量、启动时间等。 - 获取
BeanFactory
(obtainFreshBeanFactory()
)—— 只是将DefaultListableBeanFactory
Bean 工厂刷新状态为已刷新。 - 配置
BeanFactory
(prepareBeanFactory()
)—— 获取到Bean 工厂后,开始准备Bean 工厂,主要是进行功能扩展,入设置类加载器、Aware 接口支持等,这里还设置了SPEL 表达式解析器。 - 执行 Bean工厂后置处理器 (
invokeBeanFactoryPostProcessors()
)—— 修改或增强 Bean 定义(如@Configuration
类处理)。 - 注册 Bean 后置处理器(
registerBeanPostProcessors()
)—— 主要是将这些后置处理器进行分类,并添加到Bean 工厂中)。 - 初始化事件广播器、消息源等(
initMessageSource()
,initApplicationEventMulticaster()
)。 - 初始化非懒加载的单例 Bean(
finishBeanFactoryInitialization()
)—— 触发依赖注入和初始化。 - 完成刷新(
finishRefresh()
)—— 发布ContextRefreshedEvent
事件,标志容器就绪。
4.afterRefresh(后处理)
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
Bean的注册、实例化、初始化
应用上下文的启动中也包括了对Bean的注册以及实例化,由于比较重要,这里就单独将对Bean的Bean的注册、实例化、初始化提出来说下:
扫描主类
在配置容器(prepareContext)的load(context, sources.toArray(new Object[0]));
这一步,调用了BeanDefinitionLoader#load
,
private void load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
load((Class<?>) source);
return;
}
if (source instanceof Resource) {
load((Resource) source);
return;
}
if (source instanceof Package) {
load((Package) source);
return;
}
if (source instanceof CharSequence) {
load((CharSequence) source);
return;
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
这里根据不同的传参走不同的重载,也是Spring扫描和构建bean的分支点。这里的source是主类.class,所以走的是第一个if分支:
private int load(Class<?> source) {
if (isGroovyPresent()
&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
GroovyBeanDefinitionSource.class);
load(loader);
}
if (isComponent(source)) {
//以注解的方式,将启动类bean信息存入beanDefinitionMap,也就是将HelloWorldMainApplication.class存入了beanDefinitionMap
this.annotatedReader.register(source);
return 1;
}
return 0;
}
这里只将启动类注入到了beanDefinitionMap。
扫描并注册bean
在刷新容器(refresh)这一步,和Bean的装配有关的步骤主要实现是invokeBeanFactoryPostProcessors()。它调用所有实现了BeanDefinitionRegistryPostProcessor接口的类的postProcessBeanDefinitionRegistry()方法,这个方法嵌套和调用太多,是spring的核心ioc,aop实现:
其中最核心的一个实现类是ConfigurationClassPostProcessor。这个流程对于Bean,大概做了这样几件事:
处理注解
我们的Controller、Filter等服务类是怎么被扫描到的?Spring中的Servlet容器(Tomcat)是怎么启动的?其实也就是在这个步骤。
这里会去扫描并处理传入source的注解,如主类的@SpringbootApplication。@SpringbootApplication注解包含了三个重要的注解:
1.@SpringBootConfiguration
本质上就是 @Configuration
,把主类当成一个配置类注册到 Spring 容器里(将主类的 @Bean
方法(如果有)纳入容器管理)。
2.@EnableAutoConfiguration
通过 @Import(AutoConfigurationImportSelector.class)
,调用 AutoConfigurationImportSelector.selectImports(...)
。
- 利用
SpringFactoriesLoader.loadFactoryNames(...)
去读取所有依赖 JAR 中的META‑INF/spring.factories
,拿到自动配置类列表。 - 过滤、去重、排除不符合条件的配置,然后把最终的配置类名返回给容器去注册(@AutoConfigurationPackage` 只是把主类所在包及子包也作为“可自动配置的基础包”注册一遍,确保自定义配置也能被扫描到)。
3.@ComponentScan
直接触发组件扫描,扫描主类所在包及以下所有 @Controller
、@Service
、@Repository
、@Component
等注解,注册成 BeanDefinition。
把那些带注解的类包括 @Controller
——都抓出来,包装成BeanDefinition,实例化并放到 IOC 容器里。
并且这里额外提到一个注解EmbeddedServletContainerAutoConfiguration:它是是嵌入式Servlet容器的自动配置类,会先自动扫描当前环境是否引入Tomcat(如依赖配置),如果是则获取工程类、实例化Tomcat并运行Tomcat.start()
对@SpringBootApplication注解处理后,大部分的bean便都被注册到beanDefinitionMap了。当然我们知道,除了主类有这种”配置型注解”,其它类包括用户自定义类、spring内部类也会有相应的注解,哪怎么样扫描到它们呢?这就要提到第二步了:
调用BeanDefinitionLoader#load
这里调用的与上一步不用,调用的是package的重载:
private void load(Package source) {
this.scanner.scan(source.getName());
}
传入主类的包名,作为ClassPathBeanDefinitionScanner.scan
的入参。这个类又会去调用doScan,又将主类包下递归搜索,将扫描的注解进行处理,如处理常见注解(@Lazy
、@Primary
、@DependsOn
等);结合 includeFilters
和 excludeFilters
判断哪些类是候选对象(例如标注了 @Component
、@Controller
、@Service
等);扫描注解的作用域@Scope,为封装的BeanDefinition提供信息,最后注入到beanDefinitionMap。
bean的初始化和实例化
这一步就不细讲了,可以去看【spring容器启动】之bean的实例化和初始化(文末附:spring循环依赖原理)_实例化bean和初始化bean-CSDN博客。从整体过程简单来说,就是从上面步骤提到的把beanDefinitionMap注册的BeanDefinition进行解析,得到bean的元信息,再经过一系列深化处理,最终存放到三级缓存中(处理好的bean放在singletonObjects,半成品bean放在earlySingletonObjects,半成品bean工厂放在singletonFactories)
具体实现大概来说分为四步:
1. 实例化(createBeanInstance()
)
Spring 根据 BeanDefinition 中的信息(构造器、factory-method
、Supplier
),使用反射或 CGLIB 实例化对象,但此时仅是“空壳”,尚无属性。
2. 依赖填充(populateBean()
)
扫描需要注入的属性(来自 PropertyValues
、@Autowired、@Value 等),处理依赖。如果遇到未完成的 Bean,会利用三级缓存机制支持循环依赖。
3. 初始化(initializeBean()
)
处理 Aware 接口回调(如 BeanNameAware
, BeanFactoryAware
)。
执行 BeanPostProcessor.beforeInitialization()
。
执行初始化方法:@PostConstruct、afterPropertiesSet()
(来自 InitializingBean
)、以及自定义 init-method
。
执行 BeanPostProcessor.afterInitialization()
。
这里还对AOP做了一些处理,如果检测到有切面注解(@Aspect、@Poincut…),会生成一个代理–createProxy(),作为后续AOP访问的获取到的代理,作为执行函数的入口(其实的源码实现这里有点像RMI,只是本地调用”本地方法“)。
4. 注册销毁方法
将实现 DisposableBean
、配置 destroy-method
的 Bean 用 DisposableBeanAdapter
包装后,注册到销毁队列中。
循环依赖 & 三级缓存
上面提到了三级缓存,简单来说这个三级缓存就是后续例如调用getBean(String beanName)方法会去访问的缓存,用于快速地取出bean,这里复制文章中的话简短介绍一下:
singletonObjects:缓存所有构建好的bean
earlySingletonObjects:在bean构建过程中提前暴露出来的“半成品”bean
singletonFactories:用于创建“半成品”bean的工厂
(1)singletonObjects:singletonObjects中存储的都是完全构建好的bean,容器启动后在项目代码中我们通过getBean()从容器中获取bean实例时,就是直接从该缓存中获取,所以效率非常高,同时性能非常好。
(2)earlySingletonObjects:earlySingletonObjects主要保存在对象构建过程中提前暴露出来的“半成品”bean,是解决大部分循环依赖的关键。假设有两个Bean A和B,这两个Bean间存在相互依赖,且都不存在动态代理。那么当容器构建A时,实例化好A后,容器会把尚未注入依赖的A暂时放入earlySingletonObjects中,然后去帮A注入依赖。这时容器发现A依赖于B,但是B还没有构建好,那么就会先去构建B。容器在实例化好B时,同样需要帮B注入依赖,此时B发现自己依赖A,在获取A时就可以从earlySingletonObjects中获取尚未注入依赖的A引用,这样就不会阻塞B的构建,B完成构建后就又可以注入到A中,这样就解决了简单的循环依赖问题。
(3)singletonFactories:singletonFactories用来保存创建“半成品”bean的工厂实例,在Bean实例化好后,并不会直接将尚未注入依赖的bean直接放入到earlySingletonObjects中,而是将能够创建该“半成品”bean的工厂实例放入到singletonFactories中。这样我们在对外暴露“半成品”bean时就可以加入一些自定义的特殊处理逻辑。例如下图中普通切面代理就会在此处动些“手脚”。
至此,Springboot启动中对于bean的配置到此就结束了
发出结束执行的事件
listeners.started(context);
利用EventPublishingRunListener
监听器,并执行其started方法,并且将创建的Spring容器(ApplicationContext)传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的11个监听器发布启动事件。
执行Runners
this.callRunners(context, applicationArguments);
会获取容器中所有的ApplicationRunner的Bean实例、以及所用CommandLineRunner的Bean实例,并分别执行其中的run方法。这里也多用于自定义,可以自定义Springboot在启动流程时想要执行什么代码。
至此,Springboot启动流程结束,SpringApplication.run方法最终将构架的ApplicationContext返回。
Springboot的MVC流程
Springboot的mvc部分就和Spring没什么区别了,都是基于同一个 DispatcherServlet、同一套 HandlerMapping → HandlerAdapter → Controller → ViewResolver 的架构。
Springboot MVC服务的启动
上文中Springboot的run方法已经启动完毕,干的事情好像也只是返回一个ApplicationContext,那Springboot的监听端口,等待请求连接等web服务是从哪里启动的呢?
要回答这个问题,就得先搞清楚 Spring Boot 和底层 Servlet 容器(如 Tomcat)是怎么协同工作的。
Tomcat处理web请求靠的就是Servlet,如每一个.jsp在代码中都被映射成一个jsp_Servlet,而Spring mvc处理web请求并返回视图用的都是Controller。而众所周知,Spring Boot 中的 DispatcherServlet
是处理 HTTP 请求、映射到不同 @Controller
并返回视图(或数据)的核心类。
所以从Springboot的提供web服务的架构来看,Springboot MVC,Spring MVC就相当于(或者说集成在了)一个特殊的Tomcat Servlet(DispatcherServlet)。
如此而言,寻找Springboot MVC的服务启动点,就可以把眼光放在启动流程中Tomcat的启动之处。
简单来说,结合上文,Spring Boot 在启动时会创建一个内嵌的 Tomcat 容器,然后通过自动配置生成一个 ServletRegistrationBean<DispatcherServlet>
,并将其注册到这个容器中。Tomcat 在启动时会遍历所有的 ServletContextInitializer
(包括我们刚才说的 ServletRegistrationBean
),调用它们的 onStartup()
方法,将 DispatcherServlet
加入到容器的 servlet 映射表里。至此,DispatcherServlet 便正式“绑定”在指定的 URL 上,开始监听并等待客户端的连接。
在这个架构中,DispatcherServlet 则变成了Tomcat的一个特殊 Servlet,而所有的 @Controller
和其他 MVC 组件,则由 DispatcherServlet 进一步调用,完成最终的业务逻辑处理。
Springboot MVC处理请求
请求的前半部分走的是Tomcat处理,Tomcat最后会在ApplicationFilterChain#doFilter最后的internalDoFilter方法,调用Servlet#service,进入到DispatcherServlet#service
。
这里对于我们学习安全值得注意的一点的是,原生的Spring MVC只有Tomcat流程对url的字符拦截、处理做了工作。也就是在Tomcat源码流程中说的:
0–31、127 是所有 C0 控制字符, false
! " # $ < > [ \ ] ^ \ { | } ~ 。false
所有字母、数字,以及 - . / : = ? @ _ 字符都为 true
而Spring并没有对URL的安全处理多加逻辑;只有Spring Security是新加了几个Filter,其中才有Spring实现的对URL字符的拦截。
这里会先调用其父类FrameworkServlet以及FrameworkServlet父类HttpServlet先进行一个简单的处理,依据请求方法,分别doGet,doPost…先进行一个简单的解析。
最后就来到了DispatcherServlet#doDispatch
。
它做了这样几件事:
1.构造HandlerExecutionChain和寻找HandlerAdapter:根据请求方式的不同找到相应的Handlermapping;再根据url请求参数,去获取handlermapping中的Handlermethod(此处为Controller方法对应的bean),再和系统拦截器封装成一个HandlerChain;再根据这个HandlerChain去寻找适配的HandlerAdapter。
这里有很多XXXHandler,HandlerXXX。先要理清楚这些东西以及Handler和Controller的关系具体可以去看看Springboot技术文档或者说文章的适配器模式有关部分:https://blog.csdn.net/zxd1435513775/article/details/103000992
2.调用HandlerAdapter:HandlerAdapter调用对应的Handler去获得一个ModelAndView对象;这里实际上调用的就是Controller方法,得到返回值,根据返回值填写ModelAndView对象中的viewname变量。
这一步还有个值得注意的地方,viewname这个变量不仅仅是获得这个returnvalue这么简单。实际上Springboot还会在HandlerAdapter的执行链条中,获取返回值后,到ServletInvocableHandlerMethod#invokeAndHandle中去returnValueHandlers.handleReturnValue:
这里也会体现出Controller和RestController的区别–以及为什么我们宏观上用Controller会返回一个解析后的视图,而RestController只会返回一个字符串。
这里来过一下这个逻辑:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
前面通过invoke HandlerMethod(controller/restcontroller)返回一个字符串作为returnvalue。
会根据returnType去选择处理类型的handler。返回类型不就是个字符串吗?其实不然,returnType还包含了Handler等等更多信息:
而根据selectHandler
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
这里如果你用的RestController那么你会select到一个RequestResponseBodyMethodProcessor:
且由于后续返回逻辑用的是它,最后mv = ha.handle(processedRequest, response, mappedHandler.getHandler());会返回null:
而如果用的是Controller会select到ViewNameMethodReturnValueHandler
最后会返回:
3.applyDefaultViewName():对当前ModelAndView做判断,如果为null则进入defalutViewName部分处理,将URI path作为mav的viewname值。这里实际上也是第三种payload的原因。
4.processDispatchResult():渲染视图的最终点。
先处理ModelAndView,通过适配的ViewResolver将viewname解析,把mav里的viewname等等封装到一个具体的view里面,比如我们这里的Thymeleafview,其viewTemplateName里面就封装了我们的viewname。再调用Thymeleaf#render来解析视图。
如果传进去的viewname值包含::的话,即识别到这是片段表达式,会先进行预处理-即解析在变量表达式中的SPEL语句。最后根据文件名返回相应的模板文件(还会对模板文件里面的SPEL表达式进行解析–Springboot路径穿越文件上传拿shell的一个点)
最后将html写入response域。