Spring中的MergedBeanDefinitionPostProcessor有什么作用 ?
- 引言
- 调用时机
- 加载bean定义的几种方式
- postProcessMergedBeanDefinition接口作用
- 小结
引言
MergedBeanDefinitionPostProcessor
这个Bean后置处理器大家可能关注的比较少,其本身也只提供了一个bean生命周期回调接口:
public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
}
虽然这个bean生命周期回调接口可能并没有起到关键的作用,但是理解该接口的作用,还是会对我们理解整个Bean的初始化流程起着重要作用。
所以本文就来简单聊聊该接口的意义所在。
调用时机
我们先来看看该接口的调用时机:
postProcessMergedBeanDefinition
回调接口是在MergeBeanDefintion
和实例化之后进行的调用,目的是为了对合并后的BeanDefintion
进行后置处理,那么后置处理具体包含什么逻辑呢?
加载bean定义的几种方式
SpringBoot中提供了四种加载Bean定义的方式,如下所示:
对于不同方式加载得到的Bean,会封装为不同类型的BeanDefintion :
- XML 定义 Bean:GenericBeanDefinition
- @Component 以及派生注解定义 Bean:ScannedGenericBeanDefinition
- 借助于 @Import 导入 Bean:AnnotatedGenericBeanDefinition
- @Bean 定义的方法:ConfigurationClassBeanDefinition 私有静态类(ConfigurationClassBeanDefinitionReader的内部类)
BeanDefintion更多信息可参考: SpringIOC之BeanDefinition 相关类型关系
对于不同方式导入的Bean定义,如果存在重复对同一个Bean的定义,则会根据allowBeanDefinitionOverriding
属性是否设置为true,判断是否允许Bean定义的覆盖,如果不允许,则抛出异常。
而在Bean实例化之前,会进行BeanDefinition类型的归一化,即 mergeBeanFintion ,统一转换为RootBeanfintion进行后续处理。当然,这里的merge更多指代的是父子Bean定义的合并。
postProcessMergedBeanDefinition接口作用
我们可以通过上面几种方式声明Bean的定义,并且在具体的Bean类中通过@Autowired等注解进行运行时依赖注入,那么这里就会存在一个问题:
- 我们通过xml配置文件声明bean定义的时候,同样可以通过xml配置来声明依赖注入点,那么如果此时xml配置声明的依赖注入点和注解方式声明的依赖注入点产生重叠了,那么此时谁的优先级更高呢?
为了处理这个问题,Spring提供了MergedBeanDefinitionPostProcessor这个Bean后置处理器,由其负责处理xml配置的依赖注入点和注解配置的依赖注入点重叠问题。
这里以处理@Autowired和@Value注解的AutowiredAnnotationBeanPostProcessor为例,看看它的postProcessMergedBeanDefinition方法都做了什么事情:
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
findAutowiringMetadata方法负责寻找当前bean的字段和方法上使用@Autowired和@Value注解声明的依赖注入点,并为每个依赖注入点封装一个InjectElement,然后为当前bean创建一个InjectionMetadata,负责管理当前bean上所有InjectElement:
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs){
// InjectionMetadata会被缓存起来--key为beanName
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
//双重锁机制,确保单例
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
//为当前bean构建InjectionMetadata
metadata = buildAutowiringMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
//遍历当前类上所有属性,寻找到存在相关注解的属性
ReflectionUtils.doWithLocalFields(targetClass, field -> {
AnnotationAttributes ann = findAutowiredAnnotation(field);
//排除静态属性的注入
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation is not supported on static fields: " + field);
}
return;
}
//从注解中取出required属性--表明是否必须注入成功
boolean required = determineRequiredStatus(ann);
//封装为AutowiredFieldElement后返回
currElements.add(new AutowiredFieldElement(field, required));
}
});
//遍历当前bean所有方法,寻找存在相关注解的方法,并且方法不是静态的,封装为AutowiredMethodElement后返回
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
...
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
...
return;
}
...
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
elements.addAll(0, currElements);
//注入依赖注入一并处理当前父类上标注的相关依赖注入点
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
//创建一个InjectionMetadata返回,InjectionMetadata管理当前bean中所有依赖注入点
return new InjectionMetadata(clazz, elements);
}
}
metadata.checkConfigMembers(beanDefinition);
方法考虑可能存在多个注解(例如 @Autowired、@Resource)同时标注在同一个属性上,因此需要避免重复处理的问题。
在Spring中,多个注解可以同时标注在同一个属性上,用于指定不同的依赖注入方式或配置信息。但是,这可能导致在处理依赖注入时重复处理同一个属性,从而引发错误或不一致的行为。
为了避免重复处理,checkConfigMembers() 方法会检查配置类中的成员元素,并通过 RootBeanDefinition 的 registerExternallyManagedConfigMember() 方法将已处理的成员标记为外部管理的配置成员。这样,在Spring容器后续的处理过程中,如果遇到同一个成员被多次标注的情况,Spring容器会忽略重复的处理,并保持一致性。
举例来说,假设在配置类中有一个属性 myDependency,同时被 @Autowired 和 @Resource 注解标注:
@Autowired
@Resource
private MyDependency myDependency;
在调用 checkConfigMembers() 方法时,它会检查 myDependency 属性是否已经被标记为外部管理的配置成员。如果没有被标记,它会将其注册为外部管理的配置成员。这样,在Spring容器后续的处理过程中,如果遇到重复的依赖注入标记,例如另一个地方使用了 @Resource 注解标注了 myDependency,Spring容器会忽略重复的处理,保持一致性。
总结:checkConfigMembers() 方法的作用之一是考虑可能存在多个注解同时标注在同一个属性上的情况,避免重复处理。通过将已处理的成员标记为外部管理的配置成员,它确保Spring容器在处理依赖注入时不会重复处理同一个属性。
该方法具体源码如下:
public void checkConfigMembers(RootBeanDefinition beanDefinition) {
Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());
for (InjectedElement element : this.injectedElements) {
Member member = element.getMember();
if (!beanDefinition.isExternallyManagedConfigMember(member)) {
beanDefinition.registerExternallyManagedConfigMember(member);
checkedElements.add(element);
}
}
this.checkedElements = checkedElements;
}
小结
MergedBeanDefinitionPostProcessor后置处理器在Spring的实际应用中起到了两个作用:
- 初始化当前bean的InjectionMetadata缓存
- 过滤掉已经处理过的依赖注入点
当然,这只是Spring中给出的应用,我们也可以在该接口中玩出更多的花样。