题记
做为分析Spring是如何将@ComponentScan注解属性basepackages指定的包路径下的资源通过I/O来读取,通过ASM来解析类中的注解元数据(MetadataReader→AnnotationMetadata)并构造成BeanDe-finition这个过程的最后最后一篇文章。
在文章开始前先总结下前面两篇文章讲了些什么。
从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?
在第一篇文章中,我们分析了在AbstractApplicationContext的refresh方法中是如何完成指定包路径下的资源加载,这里面涉及到了JDK提供的I/O流,以及递归实现。另外和大家所想象的直接利用JVM提供的反射技术来获取类元信息不同的是,Spring是基于ASM技术来读取字节码数据来获取自己所需要的信息。在读取.class文件时候,Spring使用了NIO技术,在FileSystemResource类的getInputStrea-m方法有所体现,感兴趣的小伙伴可以阅读下。
获取到类元数据后,接下来就是进行两次筛选,把接口和没有添加@Lookup注解的抽象类以及内部类和未添加标识注解的类过滤掉。最后把筛选过后的BeanDefinition返回。
从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(二)?(https://www.notion.so/ComponentScan-IoC-BeanDefinition-f846a8c8a8d049a1a9be7b1121d0890e)
在第二篇文章中,我们基于上一步返回的BeanDefinition来分析Spring接下来的处理逻辑,还有为什么同样的方法会执行N次。主要是因为对于筛选过后的类,Spring还没有解析它们中的注解信息,因此需要重复执行执行。
还解释了如果在父类中添加应用上下文提供的注解,Spring是否支持?如果在接口中定义默认方法,在这些默认方法上添加@Bean注解,Spring是否支持?如果支持,在源码中是如何体现的以及Confi-gurationClass和SourceClass的区别。
第二篇文章的重点是Spring动态可插拔组件的基石-@Import注解。我们自定义注解中@Import注解是如何被找到的,以及@Import注解可以导入的三种类型。这有助于我们去理解Spring AOP和声明式事务以及MyBatis和Spring是如何整合的。该注解也属于Spring元注解中的一部分。
由于本篇文章是基于前面两篇文章的基础来进行分析,因此小伙伴们先阅读前面两篇文章,会更便于理解。
源码分析
总结完毕,我们把目光回到梦开始的地方-ConfigurationClassPostProcessor的processConfigBeanDe-finitions方法中。
可以说前面两篇文章都是围绕在该方法中调用ConfigurationClassParser的parse方法来分析,接下来就是分析这之后的代码。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
执行完ConfigurationClassParser的parse方法后,首先从parser中获取所有的ConfigurationClass,然后构造成一个Set集合,从该集合中移除掉哪些已经解析过的ConfigurationClass。
判断当前成员属性reader是否为空(注意这段代码是位于一个do…while循环中,第一次循环时为空),如果为空,则直接创建一个ConfigurationClassBeanDefinitionReader实例。需注意的是,这里创建的不是什么AnnotationBeanDefinitionReader,也和其没有任何关系,这点可以从类关系图中看出。值得重点分析的就是接下来调用的reader的loadeBeanDefinitions方法。
private ConfigurationClassBeanDefinitionReader reader;
// ConfigurationClassPostProcessor#processConfigBeanDefinitions 代码片段
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
可以看到在该方法中首先创建了TrackedConditionEvaluator实例,然后遍历传入的ConfigurationCla-ss调用loadBeanDefinitionForConfigurationClass方法。
// ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
在该方法中首先调用传入的TrackedConditionEvaluator实例的shouldSkip方法来判断当前Configurat-ionClass是否需要跳过处理。如果在这里判定为需要跳过,那么将会从IoC容器中移除该BeanDefiniti-on。
如果判定不需要跳过,接下来判断当前ConfigurationClass是否是通过@Import注解导入的,如果判断成立,调用registerBeanDefinitionForImportedConfiguration方法。获取当前ConfigurationClass中的beanMethods属性,这是一个集合,遍历该集合,调用loadBeanDefinitionForBeanMethod方法。
调用loadBeanDefinitionsFromImportedResources方法来处理当前ConfigurationClass中@ImportRes-ource注解导入的资源;
调用loadBeanDefinitionsFromRegistrars方法来处理当前ConfigurationClass中@Import注解导入的I-mportBeanDefinitionRegistrar实现类。
// ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
一个个分析这些调用到的方法,首先来看TrackedConditionEvalutor类。该类是ConfigurationClassB-eanDefinitionReader的一个内部类,比较简单。只有一个成员属性skipped,这是一个HashMap,用来存储对应的ConfigurationClass是否需要跳过的布尔值。
来分析下该类唯一的一个方法-shouldSkip。首先是从成员属性skipped中根据传入的ConfigurationC-lass来获取对应的布尔值,如果不为null直接返回。
如果为null,那么首先判断传入的ConfigurationClass是否是被导入的,如果是被导入的,那么则获取所有导入该ConfigurationClass的ConfigurationClass,递归调用,如果有一个方法返回false,则将all-Skipped修改为false,该值默认为true。如果遍历完所有的ConfigurationClass,allSkipped属性依旧为true,那么则将skip设为true,将处理结果保存进skipped这个Map中,返回。
可能有不少小伙伴没明白这是什么意思,要搞明白这个就需要搞明白@Import注解的作用以及解析过程 从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(二)? 就很简单了。
TrackedConditionEvaluator的shouldSkip方法只是用来解析被导入Bean和导入被导入Bean的Bean的关系,是否需要跳过的逻辑判断还是交由ConditionEvaluator的shouldSkip方法来完成。
另外需要注意的一点是Spring认为通过内部类也是被导入的类,因此这套规则同样适用。
// ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator
private class TrackedConditionEvaluator {
private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();
public boolean shouldSkip(ConfigurationClass configClass) {
Boolean skip = this.skipped.get(configClass);
if (skip == null) {
if (configClass.isImported()) {
boolean allSkipped = true;
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
if (!shouldSkip(importedBy)) {
allSkipped = false;
break;
}
}
if (allSkipped) {
// The config classes that imported this one were all skipped, therefore we are skipped...
skip = true;
}
}
if (skip == null) {
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
}
this.skipped.put(configClass, skip);
}
return skip;
}
}
假设现在有三个类要交给IoC容器管理,它们分别为A、B、ImportBean,A和B都是正常的通过@Co-mponent或者其派生注解来标识的,而ImportBean则是在在类A和类B中添加@Import注解导入。
那么当shouldSkip方法解析到ImportBean的ConfigurationClass时,首先判断是否是被导入的,判断成立,然后获取是哪些ConfigurationClass导入的ImportBean,这里获取到就是A和B的Configuratio-nClass。
获取到A的ConfigurationClass递归调用shouldSkip方法时,还是先从skipped缓存中获取对应的处理结果,如果有直接返回,但如果没有的话,这时的是否是被导入的判断就不成立了,然后直接执行ConditionEvaluator的shouldSkip方法。
这里Spring设计的很有意思的一点在于。如果在调用ConditionEvaluator的shouldSkip方法处理A的时候返回值是false,那么这个循环将会结束,不会再去处理B,也不会去修改allSkipped变量的值。这时候就会调用ConditionEvaluator的shouldSkip方法去计算ImportBean。
而如果调用ConditionEvaluator的shouldSkip方法计算出来的A和B的返回值都是true,那么直接将skip变量设为true,不会去计算ImportBean。
也就是说ImportBean也可以添加@Conditional注解来指定自己是否装配,但ImportBean通过@Con-ditional注解指定的Condition实现类实现的mtaches不一定会被调用,这取决于A和B。
要想测试出这种效果需要将A和B中@Conditional注解指定的类实现ConfigurationCondition接口,而不是直接实现Condition接口,并修改getConfigurationPhase返回值为ConfigurationPhase.REGISTE-R_BEAN。
@Component
@Conditional(ACondition.class)
@Import(ImportBean.class)
public class A {}
public class ACondition implements ConfigurationCondition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
}
@Component
@Import(ImportBean.class)
@Conditional(BCondition.class)
public class B {}
public class BCondition implements ConfigurationCondition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
}
@Conditional(ImportBeanCondition.class)
public class ImportBean {
}
public class ImportBeanCondition implements ConfigurationCondition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
}
由此可以得出一个结论,如果导入类(可能有多个)认为被导入类应该导入,那么到底要不要被导入就交由被导入类自己决定。而如果导入类(可能有多个)认为被导入类不应该被导入,那么被导入类就不能决定自己到底要不要被导入。
接下来分析regitserBeanDefinitionForImportedConfigurationClass方法,该方法的功能的便是为通过@Import注解导入的普通类生成BeanDefinition然后注册进IoC容器中。
可以看到这里为通过@Import注解导入的普通类创建的BeanDefinition类型为AnnotatedGnericBean-Definition,而不是前面对于从磁盘扫描的Class创建的ScannedGenericBeanDefinition。
调用ScopeMetadataResolver的resolveScopeMetadata方法来解析类的作用域并设置到BeanDefiniti-on中。
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
AnnotationMetadata metadata = configClass.getMetadata();
AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
configBeanDef.setScope(scopeMetadata.getScopeName());
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
configClass.setBeanName(configBeanName);
if (logger.isTraceEnabled()) {
logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
}
}
调用ImportBeanNameGenerator根据BeanDefinition来生成BeanName。虽然这里使用的是FullyQual-ifiedAnnotationBeanNameGenerator,但该类并未实现generateBeanName方法,所以最终调用的还是AnnotationBeanNameGenerator的方法。
但通常情况下通过@Import注解导入的类都不会在类上面添加Spring IoC或Context相关注解,所以这里最终还是调用buildDefaultBeanName方法,而FullyQualifiedAnnotationBeanNameGenerator重写了该方法,这也是其实现的唯一一个方法。在其实现中,直接返回全限定名,并没有像父类的build-DefaultBeanName方法那样通过Java的内省机制来将类名首字母小写。
因此,通过@Import注解导入的普通Bean(未实现ImportSelector和ImportBeanDefinitionRegistry接口的Bean)其在IoC容器中beanName通常都是全限定名。
public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
return beanClassName;
}
}
调用AnnotationConfigUtils的processCommonDefinitionAnnotations方法来解析通用注解,这里说的通用注解指@Lazy、@Primary、@DependsOn、@Role、@Description这五个注解。
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"));
}
}
之后是设置作用域代理模型,最重要的是调用BeanDefinitionRegistry的registerBeanDefinition方法将BeanDefinition信息注册到IoC容器中。
处理完通过@Import或者内部类方式注册的Bean后,接下来便是处理通过@Bean方法注册的Bean。获取当前ConfigurationClass的beanMethods属性,然后遍历调用loadBeanDefinitionForBeanMethod方法。
// ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass 方法片段
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
这个方法太长,并且大部分代码都是从@Bean注解中获取信息设置BeanDefinition中去,所以就不逐行分析了,我就把值得分析的一部分代码复制出来。
首先可以看到对于@Bean注解注册的Bean,Spring使用的是BeanDefinition类型为ConfigurationClas-sBeanDefinition。
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
//
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
Assert.state(bean != null, "No @Bean annotation attributes");
// Consider name and any aliases
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
if (metadata.isStatic()) {
// static @Bean method
if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
}
else {
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
}
beanDef.setUniqueFactoryMethodName(methodName);
}
else {
// instance @Bean method
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
另一个值得分析的点就是static和非static,可以看到对于static修饰的并标记了@Bean的方法设置其BeanClass属性以及uniqueFactoryMethodName属性,而对于非static修饰的并且标记了@Bean注解的方法则设置BeanDefinition的factoryBeanName属性。
因为这涉及到Bean的实例化以及@Configuration注解,这里就不分析原因,对于static修饰的并且标记了@Bean注解的方法,无论定义该方法的类中有没有添加@Configuration注解,每次调用这个方法都会产生不同实例。
而如果是非static修饰的并且添加了@Bean注解的方法,并且定义这个方法的类也添加了@Configur-ation注解,那么不管调用这个方法多少次,返回的实例都是同一个。
loadBeanDefinitionsFromImportedResources这个方法就不展开分析了,因为这部分是使用XmlBean-DefinitionReader来解析通过@ImportResource导入的资源。
// ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
最后就是loadBeanDefinitionsFromRegistrars方法,在第二篇文章中讲到@Import注解的时候,曾分析通过实现ImportSelector导入的类会被立即解析,而对于实现ImportBeanDefinitionRegistrar接口来注册的BeanDefinition到这里才会被注册。
// ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
// ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}