开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天
Spring Bean生命周期,从入门到进阶:
【Spring Bean生命周期】小白也能看懂的入门篇
【Spring Bean生命周期】高手如何解读源码(一) - 资源的扫描和注册
【Spring Bean生命周期】高手如何解读源码(二) - Bean的生命周期
1 从IOC用到的设计模式开始
在学习Spring的IOC源码之前,我们有必要先对Spring的IOC用到设计模式有所了解,以便我们能更好的理解作者的设计意图,主要有下面几种:
1)工厂模式(Factory Pattern):通过BeanFactory和ApplicationContext来创建对象。首先可以解决bean之间的依赖问题,达到松耦合的效果;其次可以在实例化bean过程中,进行一些额外的处理;
2)单例模式(Singleton Pattern):在Spring中的Bean默认的作用域就是singleton单例的。单例模式的好处在于对一些重量级的对象,省略了重复创建对象花费的时间,减少了系统的开销;其次是使用单例可以减少new操作的次数,减少了GC线程回收内存的压力。
3)模板模式(Template Pattern):spring框架中调用refresh()
方法完成上下文的启动,即模板方法。模版模式的特点:从头至尾,各个属性的位置都是固定的,是一个大而全的东西,固定了流程,按照模版来就行。
4)策略模式(Strategy Pattern):spring在加载bean定义信息的时候,来源可能是xml,可能是注解,也可能是properties等等。而spring都是通过BeanDefinitionReader这个接口来解析的。这种不用考虑具体来源,就可以完成即系的方式就是策略模式。
2 一切代码从main()开始
找到项目的启动类SpringApplication
,整个项目从SpringApplication的run()
方法开始,我们直接进入到SpringApplication类的run()方法中,去掉非核心逻辑,看下主要有几个动作:
public ConfigurableApplicationContext run(String... args) {
// 1. 创建Spring上下文,此处生成AnnotationConfigServletWebServerApplicationContext类型
context = createApplicationContext();
```
// 2. 刷新上下文的准备阶段
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
```
// 3. 刷新Spring上下文
refreshContext(context);
......
}
2.1 创建Spring上下文
- 第一步,采用策略模式,生成
AnnotationConfigServletWebServerApplicationContext
类型实例。 在Spring Boot工程中,应用类型有三种,定义在枚举类WebApplicationType中,
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
}
究竟启动哪种类型应用,取决于加载的类进行判断,具体实现可看里面的deduceFromXXX()
方法,此处不再展开,Debug可知此时类型为SERVLET
,返回生成的AnnotationConfigServletWebServerApplicationContext
实例。
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}
catch (Exception ex) {
}
};
- 第二步,调用AnnotationConfigServletWebServerApplicationContext的构造函数。其中对postProcessBeanFactory()方法进行了重写,在下面刷新上下文中会触发调用,此处暂且不展开。
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext implements AnnotationConfigRegistry {
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
// postProcessBeanFactory()方法被重写,在此处进行扫描
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.postProcessBeanFactory(beanFactory);
if (this.basePackages != null && this.basePackages.length > 0) {
this.scanner.scan(this.basePackages);
}
if (!this.annotatedClasses.isEmpty()) {
this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
}
}
}
2.2 刷新上下文的准备阶段
从IOC角度来看,这个阶段主要是将启动类注册
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
```
......
// 1 拿到我们的启动类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 2 将启动类封装成AnnotatedGenericBeanDefinition类型,然后注册到容器中
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
```
}
2.3 刷新上下文【重点】
其中,SpringApplication
的refreshContext(context)
调用如图:
public class SpringApplication {
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}
/**
* Refresh the underlying {@link ApplicationContext}.
* @param applicationContext the application context to refresh
*/
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
}
本质上是调用了AnnotationConfigServletWebServerApplicationContext
本身的refresh()
方法,但是这个类本身没有重写refresh()方法,执行的是父类的refresh()方法,可以看下该类的UML如图:
即调用的是
AbstractApplicationContext.refresh()
,refresh()采用的模板模式,也是整个IOC实现的核心,代码如下
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 1 准备刷新上下文环境
prepareRefresh();
// 2 获取上下文中的BeanFactory,并且执行子类refreshBeanFactory()的地方 ----- <IOC-1>
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3 对BeanFactory进行填充属性
prepareBeanFactory(beanFactory);
try {
//4 BeanFactory扩展点,子类实现该接口 ----- <IOC-2>
postProcessBeanFactory(beanFactory);
//5 执行BeanFactory的后置处理器 ----- <IOC-3>
invokeBeanFactoryPostProcessors(beanFactory);
//6 注册Bean的后置处理器,在Bean创建过程中使用(提供针对实例化的before和after方法)
registerBeanPostProcessors(beanFactory);
//7 初始化国际资源处理器
initMessageSource();
//8 初始化上下文的事件多播器
initApplicationEventMulticaster();
//9 留给子类实现的扩展点,Spring boot就是在该扩展点实现Tomcat的启动的
onRefresh();
//10 将我们的事件监听器注册到多播器上
registerListeners();
//11 实例化剩余的单例,不包含懒加载类型的
finishBeanFactoryInitialization(beanFactory);
//12 最后一步刷新,Spring Cloud是从这里启动的
finishRefresh();
}
catch (BeansException ex) {
......
}
finally {
......
}
}
}
我们只针对IOC相关的方法进行解析
IOC-1: 获取上下文中的BeanFactory
在2.1节中,我们创建了应用的上下文AnnotationConfigServletWebServerApplicationContext
,并触发了GenericApplicationContext
类的构造方法如下所示,创建了beanFactory,也就是创建了DefaultListableBeanFactory
类。
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
此处的obtainFreshBeanFactory()方法,其实就是拿到我们之前创建的beanFactory。
IOC-2: postProcessBeanFactory扩展点
在2.1节第二步,我们提到过,AnnotationConfigServletWebServerApplicationContext重写了这个钩子方法,在此处会做一个实际扫描动作
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.postProcessBeanFactory(beanFactory);
if (this.basePackages != null && this.basePackages.length > 0) {
this.scanner.scan(this.basePackages);
}
if (!this.annotatedClasses.isEmpty()) {
this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
}
}
但是在Debug的时候发现,在此处扫描时basePackages
和 annotatedClasses
均为空,没有进入到扫描注册,这个先保留,后面补充下此处触发扫描的具体场景。
IOC-3:执行BeanFactory的后置处理器(重点)
在invokeBeanFactoryPostProcessors(beanFactory)
中完成了IOC容器Bean注册阶段的三个步骤,如图所示:
主要依赖于两个类实现:
- ConfigurationClassParser 主要完成Resorce的定位
- ConfigurationClassBeanDefinitionReader 主要完成BeanDefinition的载入和注册
第一步:Resource的载入(定位)
SpringBoot中实现三种资源定位:
- 主类所在包的basePackage
- SPI扩展机制实现的自动装配(比如各种starter)
- @Import注解指定的类
我们只讲第一种的实现,在2.2节中,我们就已经将主类注册到上下文,因此,此处可以拿到主类的basePackage的路径。
我们将invokeBeanFactoryPostProcessors(beanFactory)
简化,只看IOC相关的部分
final class PostProcessorRegistrationDelegate {
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
......
// 按照顺序执行下面的方法
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
}
/**
* Invoke the given BeanDefinitionRegistryPostProcessor beans.
*/
private static void invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
postProcessBeanDefRegistry.end();
}
}
}
这个方法的调用栈比较长,直接debug到ConfigurationClassParser
类的parse()
方法。
// ConfigurationClassParser 类
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
this.deferredImportSelectorHandler.process();
}
前面2.2节我们知道,启动类会被注册为AnnotatedGenericBeanDefinition类型,它继承自AnnotatedBeanDefinition,所以继续跟踪到
// ConfigurationClassParser 类
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
其中processConfigurationClass(ConfigurationClass configClass)
,此方法是解析ConfigurationClass
的主要入口,它除了主流程的调用外,它还会由于后续解析出多个配置类而被轮循和递归调用:
1.当配置类被解析出有定义了内部类,就将该内部类封装成ConfigurationClass然后调用这方法;
2.在解析ComponentScan标注后,对符合成为配置类的Bean封装成ConfigurationClass然后调用这方法;
3.在解析Import标注环节,被引入的类并非ImportSelector、ImportBeanDefinitionRegistar和DeferredImportSelector接口的实现类时,将封装成ConfigurationClass然后调用这方法。
4.在解析当前配置的父类时,稍稍有点特别,程序会跳转到processConfigurationClass方法里的稍后一点的地方,其实就是跳过是否是配置类的判断,然后这方法体的流程走下去。这么处理的逻辑可能是因为当前的类已经是配置类了,现在只是检查它父类的定义情况,所以就跳过了对父类是否配置类的判断了。
这里整体很多递归嵌套,逻辑较复杂,不在主流程里详细介绍。
第二步:BeanDefinition的载入
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
}
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
在doScan()方法中,将所有Resource转为BeanDefinition信息
// ClassPathBeanDefinitionScanner类
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
......
}
return beanDefinitions;
}
// ClassPathScanningCandidateComponentProvider类
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
for (Resource resource : resources) {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
return candidates;
}
第三步注册也是在doScan()方法中实现的
第三步:BeanDefinition的注册
/**
* Perform a scan within the specified base packages,
* returning the registered bean definitions.
* <p>This method does <i>not</i> register an annotation config processor
* but rather leaves this up to the caller.
* @param basePackages the packages to check for annotated classes
* @return set of beans registered if any for tooling registration purposes (never {@code null})
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 第二步 完成载入
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 第三步 注册
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}