持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
基于 Spring Framework v5.2.6.RELEASE
概述
在上一篇中,介绍了组件扫描的核心过程doScan方法进行了整体分析,并且分析 Spring 如何在给定的包路径下查找符合条件的类,并创建对应的 BeanDefinition。此时的 BeanDefinition 只包含了最基本的类型信息和元信息,要将其注册到容器中,还需要进行后续的处理。本文将接着上一篇文章,分析这部分流程。
BeanDefinition 后处理
再回顾一下doScan方法的源码。
// org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
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;
}
上一篇中已经重点介绍过了findCandidateComponents方法的原理,通过这个方法,Spring 从给定的包路径中扫描到所有符合条件的类,并分别将其元信息封装成一个对应的 BeanDefinition 对象。由此得到的 BeanDefinition 只包含了最基本的信息,在被注册到容器之前,还需要进一步处理,这便是这一部分要分析的内容。
这部分比较重要的内容,是判断当前的处理的 BeanDefinition 是不是 AbstractBeanDefinition 和 AnnotatedBeanDefinition 的实例,如果是的话,则执行相应的后处理操作。在之前的分析中,我们可以知道这里的 BeanDefinition 的实现类形式 ScannedGenericBeanDefinition,它是 AbstractBeanDefinition 的子类,同时实现了 AnnotatedBeanDefinition 接口,因此,这里的两个后处理操作都会被执行到。
接下来,我们分别分析这两个后处理操作。
AbstractBeanDefinition 的后处理
如果当前的 BeanDefinition 是 AbstractBeanDefinition 的实现类,则会通过postProcessBeanDefinition方法进行后处理,这个方法的源码如下。
// org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
beanDefinition.applyDefaults(this.beanDefinitionDefaults);
if (this.autowireCandidatePatterns != null) {
beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
}
}
this.autowireCandidatePatterns默认情况下是空的,因此,我们重点看第一行代码做了什么。
这里通过 BeanDefinition 的applyDefaults方法,给 BeanDefinition 的一些属性设置了默认值,方法源码如下。
// org.springframework.beans.factory.support.AbstractBeanDefinition#applyDefaults
public void applyDefaults(BeanDefinitionDefaults defaults) {
Boolean lazyInit = defaults.getLazyInit();
if (lazyInit != null) {
setLazyInit(lazyInit);
}
setAutowireMode(defaults.getAutowireMode());
setDependencyCheck(defaults.getDependencyCheck());
setInitMethodName(defaults.getInitMethodName());
setEnforceInitMethod(false);
setDestroyMethodName(defaults.getDestroyMethodName());
setEnforceDestroyMethod(false);
}
这里的默认值,大部分都依赖于方法参数传入的 BeanDefinitionDefaults 对象,在调用方法时,传入的是 ClassPathBeanDefinitionScanner 的beanDefinitionDefaults成员变量,它在声明时就初始化好了,BeanDefinition 的属性的默认值都来自这里。
private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();
AnnotatedBeanDefinition 的后处理
如果当前的 BeanDefinition 是 AnnotatedBeanDefinition 的实现类,则会通过 AnnotationConfigUtils 的processCommonDefinitionAnnotations方法,进行后处理。
// org.springframework.context.annotation.AnnotationConfigUtils#processCommonDefinitionAnnotations(org.springframework.beans.factory.annotation.AnnotatedBeanDefinition)
public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {
processCommonDefinitionAnnotations(abd, abd.getMetadata());
}
// org.springframework.context.annotation.AnnotationConfigUtils#processCommonDefinitionAnnotations(org.springframework.beans.factory.annotation.AnnotatedBeanDefinition, org.springframework.core.type.AnnotatedTypeMetadata)
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
else if (abd.getMetadata() != metadata) {
lazy = attributesFor(abd.getMetadata(), Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
}
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {
abd.setDependsOn(dependsOn.getStringArray("value"));
}
AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null) {
abd.setRole(role.getNumber("value").intValue());
}
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null) {
abd.setDescription(description.getString("value"));
}
}
虽然这个方法的代码比较多,但是逻辑非常简单,就是将 BeanDefinition 对应的类型上与 BeanDefinition 配置有关的注解找到,根据这些注解的信息,设置 BeanDefinition 对应属性的值。比如,Lazy 注解的value值就是 BeanDefinition 的lazyInit属性的值,以此类推。
BeanDefinition 注册
通过上述的后处理,BeanDefinition 已经组装完成。之后,BeanDefinition 会被封装到一个 BeanDefinitionHolder 对象中,最后通过调用registerBeanDefinition方法,将 BeanDefinition 对象注册到容器中。
registerBeanDefinition方法的源码如下。
// org.springframework.context.annotation.ClassPathBeanDefinitionScanner#registerBeanDefinition
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
向容器中注册 BeanDefinition 的工作交给了 BeanDefinitionReaderUtils 的registerBeanDefinition方法。如果你读过我之前分析 ClassPathXmlApplicationContext 初始化的源码分析文章,应该对这个方法有点印象。Spring 从 XML 配置文件中加载到 BeanDefinition 后,也是通过这个方法将其注册到容器中的。
因此,这里就不对这部分源码再做重复的分析了,你可以回顾一下之前文章中,分析registerBeanDefinition方法原理的部分。文章链接:Spring 源码阅读 09:加载 BeanDefinition 的过程(注册阶段)
总结
至此,doScan方法的全部源码就分析完了。执行完扫描操作之后,就是对 Spring 上下文的初始化操作中最重要的部分,也就是通过refresh方法启动上下文,这一部分,我也在之前的文章中做过详细的分析,可以点击下方的专栏链接阅读。