Spring常见问题解决 - 同一个类型的单例Bean找到了两个?

前言

其实这个问题,在我上一篇文章Spring - 常见编程错误之Bean的定义就有说过了。但是我在做这一块内容的时候,我还是觉得做成一个问题对应一篇文章的方式比较好,这样更具有针对性。我个人也希望自己能够在源码角度能够将自身学过的知识串联起来。因此从源码角度会分析的更多一点。

一. 单例 Bean 为何找到了两个?

首先,这类错误比较常见的提示信息是:

required a single bean, but 2 were found

1.1 案例复现

然后我们来复现一下这个问题:
1.提供一个接口,2个或以上的对应接口实现类:

public interface UserService {
    void say();
}

@Service
public class TeacherService implements UserService{
    @Override
    public void say() {
        System.out.println("Teacher");
    }
}

@Service
public class StudentService implements UserService{
    @Override
    public void say() {
        System.out.println("Student");
    }
}

2.然后在Controller类中去引入这个接口实例:

@Autowired
private UserService userService;

3.运行结果如下:
在这里插入图片描述

1.2 原理分析

同样的,我们应该从@Autowired这个注解出发。我们知道,这个注解用于根据类型对Bean进行自动装配。 那么首先我们应该追溯的是这个Bean的创建过程。

Bean的创建过程,统一在AbstractAutowireCapableBeanFactory.createBean()这个函数中。这个函数,我们需要明知的重要信息是,一个Bean的创建,包含了三大步骤:

  1. Bean实例的创建。
  2. 相关依赖的属性注入。
  3. Bean的初始化过程。

那么首先的我们应该去看外部因素,也就是@Autowired注解的使用是否和这个问题有关联?

1.2.1 @Autowired 和 BeanPostProcessor的关系

那么,@Autowired注解本身和这个Bean的创建过程有何关联呢?我们先来看下这个注解所在的包结构:
在这里插入图片描述
红圈框起来的两个是一对小俩口,我觉得可以这么理解:

  1. @Autowired注解:用来一个显式的声明。
  2. AutowiredAnnotationBeanPostProcessor:用来底层逻辑的具体实现。

在上一篇文章我们说过,BeanPostProcessor它是一个接口,可以让使用者在某个Bean被初始化操作的前后做出对应地修改操作。这里再做个额外的分享,Spring中有一个名字很像的接口,然后呢又极容易搞混:

  • BeanFactoryPostProcessor:这个处理器,是在Bean被实例化之前,允许对其元数据做出动态地修改。
  • BeanPostProcessor:这个处理器,则是在Bean被实例化之后,初始化之前/后做出对应地修改操作。

请注意的是,两者的调用时机不一样,请不要搞混了。


回到正轨,我们来继续说下AutowiredAnnotationBeanPostProcessor这个类,到目前为止,我们得知的信息有两点:

  1. @Autowired注解的实现依靠于 AutowiredAnnotationBeanPostProcessor底层逻辑。
  2. AutowiredAnnotationBeanPostProcessor又和BeanPostProcessor这个后置处理器有关联。

在上一篇文章Spring - 常见编程错误之Bean的定义我提到了,@Autowired注解的作用就是自动装配,只不过对应的Bean是一个单例罢了。但是本文的案例中,在自动装配的过程中,却发现了两个Bean?那么我们就要看下自动装配的过程了。

1.2.2 @Autowired 和 依赖注入的关系

我们再来看下Controller这个类:

@Controller
public class MyController {
	@Autowired
    private UserService userService;
}

我们再来重复一遍这样的话,一个Bean的创建分为三个步骤:实例创建、依赖注入、初始化。很明显,userService对于MyController类而言,是它的一个内部依赖对象,涉及到依赖注入阶段。因此我们来看下依赖注入的代码:

AbstractAutowireCapableBeanFactory.populateBean()

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	// ..
	for (BeanPostProcessor bp : getBeanPostProcessors()) {
		if (bp instanceof InstantiationAwareBeanPostProcessor) {
			InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
			PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
			// ..
			pvs = pvsToUse;
		}
	}
	// ..
}

这里我们可以看出,代码主要是循环调用BeanPostProcessor这个后置处理器的postProcessProperties()函数。但是看到这里的朋友们可能有疑惑了。文章不是说,BeanPostProcessor的作用不就是在初始化Bean的前后做出对应的处理吗?那不就是对应着两个方法呗?没错,看下BeanPostProcessor这个接口:

public interface BeanPostProcessor {
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}

那么postProcessProperties()函数从何而来?它来自InstantiationAwareBeanPostProcessor接口,其继承了BeanPostProcessor接口:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
	@Nullable
	default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
			throws BeansException {
		return null;
	}
}

那么回到 AutowiredAnnotationBeanPostProcessor类中,就有这个方法的具体实现:(由于和上篇文章的内容有点重复,因此这里只把关键代码的调用链贴出来)

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
	// 1.寻找注入对象的相关元数据
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	// 2.执行依赖的注入
	metadata.inject(bean, beanName, pvs);
}
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
	metadata = buildAutowiringMetadata(clazz);
}
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
	ReflectionUtils.doWithLocalFields(targetClass, field -> {
		// 寻找字段元数据信息
		MergedAnnotation<?> ann = findAutowiredAnnotation(field);
			currElements.add(new AutowiredFieldElement(field, required));
		});
		// 寻找方法元数据信息
		ReflectionUtils.doWithLocalMethods(targetClass, method -> {
			currElements.add(new AutowiredMethodElement(method, required, pd));
		});
	};
}

这里我们只需要关注两点即可:

  1. 我们的ControllerBean,其相关的依赖元数据信息在这里都被收集起来了,并且封装成了AutowiredMethodElement对象。
  2. 然后去调用对应的inject()函数,根据第一点,这里实际调用的自然而然的是AutowiredMethodElement.inject()函数。

1.2.3 依赖注入和 inject() 之间的关系

我们可以从上文直到,依赖注入其实就俩过程:

  1. 寻找目标类的相关依赖元数据信息。包括字段、方法。
  2. 执行inject()方法完成依赖注入,本质上就是通过反射赋值。

我们来看下inject()中比较重要的一点:

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Field field = (Field) this.member;
	Object value;
	// 第一次肯定没缓存,我们不考虑这个分支
	if (this.cached) {
		value = resolvedCachedArgument(beanName, this.cachedFieldValue);
	}
	else {
		DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
		desc.setContainingClass(bean.getClass());
		Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
		Assert.state(beanFactory != null, "No BeanFactory available");
		TypeConverter typeConverter = beanFactory.getTypeConverter();
		try {
			// 根据依赖的信息,找到对应的依赖并完成注入
			value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
		}
		catch (BeansException ex) {
			throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
		}
		// ...
	if (value != null) {
		ReflectionUtils.makeAccessible(field);
		field.set(bean, value);
	}
}

我们将重点转移到beanFactory.resolveDependency这个函数上:最终实现:

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
		result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
	}

	// 这里的descriptorz指的是userService这个字段,beanName指的是myController
	@Nullable
	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
		try {
			Object shortcut = descriptor.resolveShortcut(this);
			if (shortcut != null) {
				return shortcut;
			}
			// 拿到这个字段对应的类型 ---> UserService
			Class<?> type = descriptor.getDependencyType();
			// 看看是否有默认值,没有就是null
			Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
			if (value != null) {
				// ...一些converter转换
			}
			// 如果是集合类型依赖的处理
			Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
			if (multipleBeans != null) {
				return multipleBeans;
			}
			// 根据类型userService,去匹配Bean 。由于UserService我们有两个实现类,因此这里能匹配出两条数据
			// 这里只是一个候选的Bean
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
			// ...
			String autowiredBeanName;
			Object instanceCandidate;
			// 我们只关注如果匹配出来的结果有多个会怎么样
			if (matchingBeans.size() > 1) {
				// 开始匹配,确定最终该使用哪一个Bean做注入
				autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
				if (autowiredBeanName == null) {
					// 如果匹配出来的结果为null 但是呢,Required属性又是true(意思就是说这个属性必须要注入,要有值)
					// 或者indicatesMultipleBeans这个条件不满足
					if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
						return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
					}
					else {
						return null;
					}
				}
				instanceCandidate = matchingBeans.get(autowiredBeanName);
			}
			// ...
			return result;
		}
		finally {
			ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
		}
	}

}

总的来说就是:

  1. 根据目标类下的对应字段,我们拿到它的类型是UserService
  2. 根据类型去Spring容器中已经装配好的Bean中,去查找候选Bean。这里找到了两个。
  3. 从候选集合中做进一步的筛选,确定最终到底需要用哪个Bean为当前字段做注入操作。
  4. 如果匹配不出来或者不满足一定条件,那么就抛异常了。

那么我们应该看下,第三步中的匹配流程,这里给出debug下的截图会更直观点:
在这里插入图片描述
因此这里无法做到严格的名字匹配,因此匹配结果为null,就自然而然抛出我们案例给出的异常信息啦:
在这里插入图片描述

1.2.4 总结

我们知道@Autowired注解是通过类型去自动装配的,那么本文从源码角度来说很好地解释了这点,具体原理如下:

  1. @Autowired注解修饰的属性M,会被BeanPostProcessor后置处理器做对应地操作。具体的实现逻辑在于AutowiredAnnotationBeanPostProcessor类中。
  2. AutowiredAnnotationBeanPostProcessor类主要做两件事:1.收集M的相关元数据信息。2.将这些元数据封装成AutowiredMethodElement类。
  3. 如果某个类A中通过 @Autowired注解引入M。那么根据创建Bean的三大步骤来说,第二步的依赖注入阶段,会执行postProcessProperties()方法。从而执行了上面生成的AutowiredMethodElement.inject()函数。

inject()主要做两件事:

  1. 根据当前类的类型去Spring容器中寻找候选匹配类型。
  2. 从候选类型中进一步匹配:先看Primary,再看Priority,再根据名称做严格匹配。
  3. 由于Spring进行Bean创建的时候,beanName默认就是这个类名或者字段名(首字母小写)。因此候选Bean对于本文案例来说就是俩:studentServiceteacherService。无法和目标类userService做严格匹配。
  4. 因此匹配结果为null,程序抛出异常。

知道了原理,那么解决起来就容易了。

1.3 解决方案

解决方案一:修改下字段名称。

@Autowired
private UserService userService;
改成
@Autowired
private UserService studentService;

那么这样做匹配的时候,就是根据studentService这个名字来全匹配了:
在这里插入图片描述

解决方案二:通过Qualifier加个名称。

@Autowired
@Qualifier("studentService")
private UserService userService;

二. 关于 Bean 默认名称大小写问题

其实第一章节里面,我们可以发现 bean 的名称在案例问题中有着至关重要的地位。那么为了防止踩坑,这里讲一下Spring中生成bean的一个方法。

入口:AnnotationBeanNameGenerator.generateBeanName()

@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
	// 如果bean 显式地指定了名称,就用它
	if (definition instanceof AnnotatedBeanDefinition) {
		String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
		if (StringUtils.hasText(beanName)) {
			// Explicit bean name found.
			return beanName;
		}
	}
	// 否则,根据默认的规则去生成一个
	return buildDefaultBeanName(definition, registry);
}

看下默认生成规则:

protected String buildDefaultBeanName(BeanDefinition definition) {
	String beanClassName = definition.getBeanClassName();
	Assert.state(beanClassName != null, "No bean class name set");
	String shortClassName = ClassUtils.getShortName(beanClassName);
	return Introspector.decapitalize(shortClassName);
}

public static String decapitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        // 如果第一个字符和第二个字符都是大写,那么直接返回名称。例如SQLService,那么对应的beanName就是SQLService
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                        Character.isUpperCase(name.charAt(0))){
            return name;
        }
        // 否则就让第一个字符变成小写然后返回
        char chars[] = name.toCharArray();
        chars[0] = Character.toLowerCase(chars[0]);
        return new String(chars);
    }

说白了就是:

  1. 第一个第二个字符为大写,原样返回。
  2. 其余情况第一个字符转小写然后返回。

因此切记,通过@Autowired引入的对象,名称,或者显式指定方式下的名称,命名规则一定要对得上。否则就会报错!

例如:

@Autowired
@Qualifier("StudentService")
private UserService userService;

结果如下:StudentService这个Bean并没有命中!
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Zong_0915

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

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

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

打赏作者

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

抵扣说明:

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

余额充值