前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring中的MergedBeanDefinitionPostProcessor有什么作用 ?

Spring中的MergedBeanDefinitionPostProcessor有什么作用 ?

作者头像
大忽悠爱学习
发布2023-05-23 10:14:53
7520
发布2023-05-23 10:14:53
举报
文章被收录于专栏:c++与qt学习

Spring中的MergedBeanDefinitionPostProcessor有什么作用 ?

引言

MergedBeanDefinitionPostProcessor这个Bean后置处理器大家可能关注的比较少,其本身也只提供了一个bean生命周期回调接口:

代码语言:javascript
复制
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方法都做了什么事情:

代码语言:javascript
复制
	@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:

代码语言:javascript
复制
	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 注解标注:

代码语言:javascript
复制
@Autowired
@Resource
private MyDependency myDependency;

在调用 checkConfigMembers() 方法时,它会检查 myDependency 属性是否已经被标记为外部管理的配置成员。如果没有被标记,它会将其注册为外部管理的配置成员。这样,在Spring容器后续的处理过程中,如果遇到重复的依赖注入标记,例如另一个地方使用了 @Resource 注解标注了 myDependency,Spring容器会忽略重复的处理,保持一致性。

总结:checkConfigMembers() 方法的作用之一是考虑可能存在多个注解同时标注在同一个属性上的情况,避免重复处理。通过将已处理的成员标记为外部管理的配置成员,它确保Spring容器在处理依赖注入时不会重复处理同一个属性。

该方法具体源码如下:

代码语言:javascript
复制
	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中给出的应用,我们也可以在该接口中玩出更多的花样。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring中的MergedBeanDefinitionPostProcessor有什么作用 ?
  • 调用时机
  • 加载bean定义的几种方式
  • postProcessMergedBeanDefinition接口作用
  • 小结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档