Spring源码学习:spring使用@Bean方法注入对象时,beanName相同出现随机注入的问题

前言

当我们在配置类中通过@Bean方法注入对象时,如果设置相同的beanName,会出现注入对象不一致的问题。

1 现象

前提:设置配置类Config,实现接口MyInterface(接口中必须有一个@Bean方法),同时配置类中设置两个@Bean方法,这两个@Bean名字设置为相同如下图:
测试图
设置主类如下:

public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
		User bean = context.getBean("ceshi1");
		System.out.println(bean.getId());
	}

运行结果如下图:
结果图
可以看出多次运行的话,每次注入的对象是随机的。

2 源码探究

从源码看,对配置类解析@Bean方法时会出现不一致的顺序,具体如下

  1. 我们进入到ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry中对配置类的解析,会进入到ConfigurationClassParser.parse解析。
  2. 进入processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter)这个方法,这个方法对配置类解析如下:
    解析配置类
    进入doProcessConfigurationClass
/**
		 * 处理@Bean方法
		 */
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

@Bean方法的处理最终调用retrieveBeanMethodMetadata

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
		AnnotationMetadata original = sourceClass.getMetadata();
		Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
		if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
			// Try reading the class file via ASM for deterministic declaration order...
			// Unfortunately, the JVM's standard reflection returns methods in arbitrary
			// order, even between different runs of the same application on the same JVM.
			/**上述通过反射获取的方法是随机的不是按照声明顺序、
			 * 通过asm技术获取确定的声明顺序
			 * 由于asm读取的是class文件,无法获取类继承接口信息,所有当继承接口中由其它@Bean方法,这里将直接使用jvm反射
			 * 获取的方法信息,也就是可能是无序的,
			 */
			try {
				AnnotationMetadata asm =
						this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
				Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
				if (asmMethods.size() >= beanMethods.size()) {
					Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
					for (MethodMetadata asmMethod : asmMethods) {
						for (MethodMetadata beanMethod : beanMethods) {
							if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
								selectedMethods.add(beanMethod);
								break;
							}
						}
					}
					if (selectedMethods.size() == beanMethods.size()) {
						// All reflection-detected methods found in ASM method set -> proceed
						beanMethods = selectedMethods;
					}
				}
			} catch (IOException ex) {
				logger.debug("Failed to read class file via ASM for determining @Bean method order", ex);
				// No worries, let's continue with the reflection metadata we started with...
			}
		}
		return beanMethods;
	}

从核心逻辑可以看出

  1. 首先是通过jvm的核心反射获取@Bean修饰的方法(getAnnotatedMethods同时会获取接口中实现的@Bean方法),由于上述获取的@Bean方法是以随机顺序返回的,不是声明的顺序。
  2. 所有,又使用asm技术读取class字节码获取了@Bean方法,主要是为了确保返回与声明一致的@Bean方法。
    但是上述代码中asmMethods.size() >= beanMethods.size()这行代码导致了我们上述描述的现象,因为根据我们定义的Config类(类中定义了2个@Bean方法,继承接口中一个默认的@Bean方法)总共实现了3个@Bean方法。所以在通过反射获取时会获取到3个,通过asm读取字节码只能读取到2个。导致后边执行逻辑不会进入,直接返回了反射获取的@Bean方法,从而导致后续处理@Bean方法时会出现随机注入的问题。
    所以该问题的根本在于jvm反射获取方法不是返回声明时的确定顺序。

3 测试

3.1 实现接口情况

如上述现象描述,我们debug到retrieveBeanMethodMetadata方法结果如下图
结果图
可以看出此时用asm处理顺序确定是失效的,没有进入处理逻辑

3.2 去掉实现类或者删除接口中@Bean方法

结果图如下
结果图2
此时由于反射和asm获取的@Bean方法个数一致,从而进入了处理逻辑,此时运行返回的结果是一致的,始终是声明的顺序。

总结

从上述现象分析,jvm的反射获取方法不是确定顺序对一些特殊的情况可能出现问题,但是通过asm可以确保这一问题的解决。但是对于涉及到接口的情况,asm处理起来会有些局限。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LamaxiyaFc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值