Sping中怎么处理@Bean注解bean同名的问题

  • 首先明确
  • @Bean注解的bean同名的两种情况
  • 情况一
    • 源码分析
    • spring如何完成覆盖或者保留的?
    • 覆盖规则分析
  • 情况二
    • BeanDefinition的生成:
    • 重载工厂方法的选择
      • 基本思想:
        • 源码分析

首先明确

@Bean注解的两个方法返回对象是同一类型的时候,才会出现覆盖问题,如果两个bean不是同一个类型,直接就报错了。所以下述的情况都是@Bean注解的方法返回的bean是同类型同名的bean,这样才有讨论的必要。

    @Bean("sim1")public Sim2 sim2(){Sim2 sim1 = new Sim2();return sim1;}@Bean("sim1")public Sim1 sim1(){Sim1 sim1 = new Sim1();return sim1;}报错:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified
bean name 'sim1' for bean class [springdemo.entity.sim.Sim1] conflicts with existing, non-compatible bean definition of same name and class [springdemo.entity.sim.Sim2]

@Bean注解的bean同名的两种情况

  1. @Bean注解的方法:方法名不同但是返回对象类型相同,@Bean注解中显式指定相同的beanName
  2. @Bean注解:不同的重载方法,且bean同名

情况一

@Bean注解的方法:方法名不同但是返回对象类型相同,@Bean注解中显式指定相同的beanName

 @Bean("sim1")public Sim1 sim1(){System.out.println("public Sim1 sim1()");Sim1 sim1 = new Sim1();sim1.setName("sim1");return sim1;}@Bean("sim1")public Sim1 sim2(){System.out.println("public Sim1 sim2()");Sim1 sim1 = new Sim1();sim1.setName("sim1");return sim1;}
输出结果如下:
public Sim1 sim1()

源码分析

在以下方法中会判断现在的beanName在现有的beanDefinitionMap中是否已存在,

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(BeanMethod)

然后在以下方法中决定是否覆盖。

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.isOverriddenByExistingDefinition(BeanMethod, String)

isOverriddenByExistingDefinition()部分具体代码如下:

       BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);// Is the existing bean definition one that was created from a configuration class?// -> allow the current bean method to override, since both are at second-pass level.// However, if the bean method is an overloaded case on the same configuration class,// preserve the existing bean definition.if (existingBeanDef instanceof ConfigurationClassBeanDefinition) {ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef;//获得existingBean所在的配置类名,和当前要创建的bean进行比较:如果是 same config class,则mbd中保留先前的if (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName())) {if (ccbd.getFactoryMethodMetadata().getMethodName().equals(ccbd.getFactoryMethodName())) {//设置mbd中isFactoryMethodUnique为false,即标记该bean的工厂方法不唯一,这样后面instantiateUsingFactoryMethod才会处理ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName());}return true;}else {return false;}}

可以看出是否覆盖的策略如下

  • 如果是来自不同层级的factory method,允许覆盖,isOverriddenByExistingDefinition()方法返回false
  • 如果是来自同一配置类,保留先前的beanDefinition,isOverriddenByExistingDefinition()返回true。
    (除此之外该方法还会设置beanDefinition中isFactoryMethodUnique字段为false,即标记该factory method不唯一)

spring如何完成覆盖或者保留的?

分析onfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod 以下源码片段可以看出:

        //如果需要保留先前注册的bean,isOverriddenByExistingDefinition()方法返回true,//该方法就会进入该条件代码块,直接返回if (isOverriddenByExistingDefinition(beanMethod, beanName)) {if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +"' clashes with bean name for containing configuration class; please make those names unique!");}return;}//如果需要覆盖,就会继续执行接下来的代码,生成新的beanDefinition并进行更新,新的beanDefinition就会保留新的factory method

覆盖规则分析

  • 通过@ComponentScan扫描进来的优先级是最低的,原因就是它扫描进来的Bean定义是最先被注册的,也就是说同文件下@Bean的会生效,@ComponentScan扫描进来不会生效。
  • @Import引入的配置类中的bean会被当前配置类中的同名bean覆盖。
  • 不同配置文件中存在同名Bean,后解析的配置文件会覆盖先解析的配置文件。

此处需要注意的是:配置文件的先后顺序其实会受到@Order来控制,只是若没有@Order注解的话就按照传入的顺序执行解析。

情况二

@Bean注解:不同的重载方法,且返回同名bean

BeanDefinition的生成:

ConfigurationClassBeanDefinitionReader#isOverriddenByExistingDefinition

在情况一中我们知道,该方法碰到来自同一配置类的同名bean,会设置其beanDefinition对象中isFactoryMethodUnique字段为false,以此标记该bean的工厂方法不唯一,为之后利用factory method实例化bean做准备。

重载工厂方法的选择

利用factory method实例化bean主要在以下方法中完成:
ConstructorResolver.instantiateUsingFactoryMethod()
在该方法中完成了重载方法的选择

基本思想:

  1. if(mbd.isFactoryMethodUnique)工厂方法唯一:工厂方法不存在重载或者@Bean注解的beanName相同但方法名不同的方法

  2. 如果不唯一则继续处理:通过反射获取该配置类下所有方法保存到集合rawCandidates

  3. 遍历rawCandidates,找出所有符合要求的重载的candidate,判断条件如下:
    1、首先是实例还是静态要和当前从bd中解析出的结果相同
    2、mbd.isFactoryMethod(candidate)比较当前方法名和bd中保存的FactoryMethodName是否相同:

  4. 给所有candidate排序:
    1、先比较可见性public的排在前面
    2、 否则比较参数个数,优先选参数多的

  5. createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring, candidates.size() == 1)
    根据已经解析的构造器参数值、mbd等,创建一个参数数组以作为匹配标准选择要调用构造函数或工厂方法。

  6. 寻找最小类型差异值minTypeDiffWeight:
    对所有重载方法(candidates)进行遍历, 并计算每个方法参数列表的typeDiffWeight,该值代表了该方法的参数和beanDefinition中保存的参数列表的匹配程度,选取参数差异值最小的方法作为要使用的工厂方法。

  7. 对于具有相同数量参数的方法,
    • 如果它们具有相同的typeDiffWeight,则收集此类候选方法放入ambiguousFactoryMethods并最终引发歧义异常。
    • 但是仅在isLenientConstructorResolution==false模式下执行该检查。
    • !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes()条件能够显式忽略掉重写的方法(因为重写具有相同的参数签名)

源码分析
    public BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {BeanWrapperImpl bw = new BeanWrapperImpl();this.beanFactory.initBeanWrapper(bw);Object factoryBean;Class<?> factoryClass;boolean isStatic;String factoryBeanName = mbd.getFactoryBeanName();//首先,在bd中找是否有factoryBeanif (factoryBeanName != null) {if (factoryBeanName.equals(beanName)) {//factoryBean不能和要创建的bean一样,即不能在配置类中配置config本身的beanthrow new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,"factory-bean reference points back to the same bean definition");}factoryBean = this.beanFactory.getBean(factoryBeanName);//从bean工厂中返回factoryBeanif (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {throw new ImplicitlyAppearedSingletonException();//bean在它的factoryBean创建的过程中已经隐式地被创建了}factoryClass = factoryBean.getClass();isStatic = false;}else {// It's a static factory method on the bean class.// 如果bean definition中没有传入一个factory-bean,// 而是传入一个class对象或者一个使用依赖注入配置的factory object本身的实例变量。// 那么该命名工厂方法可能是一个静态方法if (!mbd.hasBeanClass()) {throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,"bean definition declares neither a bean class nor a factory-bean reference");}factoryBean = null;factoryClass = mbd.getBeanClass();//如果是静态方法,就获取配置类的class对象isStatic = true;}Method factoryMethodToUse = null;ArgumentsHolder argsHolderToUse = null;Object[] argsToUse = null;if (explicitArgs != null) {argsToUse = explicitArgs;}else {Object[] argsToResolve = null;synchronized (mbd.constructorArgumentLock) {factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod;//程序包可见的字段,用于缓存已解析的构造函数或工厂方法if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) {// Found a cached factory method...// 如果缓存中存在,那么从缓存中获取factory method参数argsToUse = mbd.resolvedConstructorArguments;if (argsToUse == null) {argsToResolve = mbd.preparedConstructorArguments;}}}//如果缓存中获取到参数if (argsToResolve != null) {argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true);}}//缓存中没有,尝试使用所有具有该名称的方法,以查看它们是否与给定参数匹配。if (factoryMethodToUse == null || argsToUse == null) {// Need to determine the factory method...// Try all methods with this name to see if they match the given arguments.factoryClass = ClassUtils.getUserClass(factoryClass);//根据代理类获取原配置类List<Method> candidates = null;//工厂方法唯一:工厂方法不存在重载或者@Bean注解的beanName相同但方法名不同的方法if (mbd.isFactoryMethodUnique) {if (factoryMethodToUse == null) {factoryMethodToUse = mbd.getResolvedFactoryMethod();}if (factoryMethodToUse != null) {candidates = Collections.singletonList(factoryMethodToUse);}}if (candidates == null) {candidates = new ArrayList<>();//通过反射获取该factoryClass下所有方法Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);//遍历,找出所有符合要求的重载的candidatefor (Method candidate : rawCandidates) {//1、首先是实例还是静态要和当前从bd中解析出的结果相同// 2、mbd.isFactoryMethod(candidate)比较当前方法名和bd中保存的FactoryMethodName是否相同://     只有重载的方法才会返回true,所以重载方法都可以作为candidate//    @Bean注解解析出的beanname相同但是方法名不同的多个方法,只会有一个是bd中设置的工厂方法,其他会被过滤掉。if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {candidates.add(candidate);}}}//如果没有重载方法,且没有显式传递参数,且mbd中没有提前存有构造函数参数值if (candidates.size() == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {Method uniqueCandidate = candidates.get(0);if (uniqueCandidate.getParameterCount() == 0) {//工厂方法的参数个数为0mbd.factoryMethodToIntrospect = uniqueCandidate;synchronized (mbd.constructorArgumentLock) {mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;mbd.constructorArgumentsResolved = true;mbd.resolvedConstructorArguments = EMPTY_ARGS;}//FactoryMethod参数为0个的情况bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, uniqueCandidate, EMPTY_ARGS));return bw;}}//存在多个重载的factoryMethod 如:配置类中声明的是多个重载的工厂方法if (candidates.size() > 1) {  // explicitly skip immutable singletonList//给所有candidate排序:// 1、先比较可见性public的排在前面// 否则比较参数个数,优先选参数多的candidates.sort(AutowireUtils.EXECUTABLE_COMPARATOR);}// getResolvedAutowireMode返回bean的装配方式 // autowiring指示是否为构造器装配ConstructorArgumentValues resolvedValues = null;boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);//寻找最小类型差异值minTypeDiffWeightint minTypeDiffWeight = Integer.MAX_VALUE;Set<Method> ambiguousFactoryMethods = null;int minNrOfArgs;if (explicitArgs != null) {minNrOfArgs = explicitArgs.length;}else {//我们没有以代码方式显式传递参数,因此我们需要解析在beanDefinition中保存的构造函数参数列表中指定的参数。// We don't have arguments passed in programmatically, so we need to resolve the// arguments specified in the constructor arguments held in the bean definition.if (mbd.hasConstructorArgumentValues()) {//beanDefinition中保存了构造函数参数ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();resolvedValues = new ConstructorArgumentValues();minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);}else {//beanDefinition中没有保存参数minNrOfArgs = 0;}}//保存出现异常的原因,最后统一处理LinkedList<UnsatisfiedDependencyException> causes = null;//对所有重载方法(candidates)进行遍历,// 并计算每个方法参数列表的typeDiffWeight---代表了该方法的参数和bd中保存的参数列表的匹配程度,// 遍历并选取参数差异值最小的方法作为要使用的工厂方法for (Method candidate : candidates) {int parameterCount = candidate.getParameterCount();//获取方法的参数个数if (parameterCount >= minNrOfArgs) {ArgumentsHolder argsHolder;Class<?>[] paramTypes = candidate.getParameterTypes();if (explicitArgs != null) {// Explicit arguments given -> arguments length must match exactly.if (paramTypes.length != explicitArgs.length) {continue;}argsHolder = new ArgumentsHolder(explicitArgs);}else {// Resolved constructor arguments: type conversion and/or autowiring necessary.try {String[] paramNames = null;ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();if (pnd != null) {paramNames = pnd.getParameterNames(candidate);}//根据已经解析的构造器参数值、mbd等,创建一个参数数组以作为匹配标准选择要调用构造函数或工厂方法。argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,paramTypes, paramNames, candidate, autowiring, candidates.size() == 1);}catch (UnsatisfiedDependencyException ex) {if (logger.isTraceEnabled()) {logger.trace("Ignoring factory method [" + candidate + "] of bean '" + beanName + "': " + ex);}// Swallow and try next overloaded factory method.if (causes == null) {causes = new LinkedList<>();}causes.add(ex);continue;}}int typeDiffWeight = (mbd.isLenientConstructorResolution() ?argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));// Choose this factory method if it represents the closest match.//如果它表示最接近的匹配项,则选择此工厂方法if (typeDiffWeight < minTypeDiffWeight) {factoryMethodToUse = candidate;argsHolderToUse = argsHolder;argsToUse = argsHolder.arguments;minTypeDiffWeight = typeDiffWeight;ambiguousFactoryMethods = null;}//找出歧义:对于具有相同数量参数的方法,// 如果它们具有相同的类型差权重,则收集此类候选方法放入ambiguousFactoryMethods并最终引发歧义异常。 // 但是,仅在isLenientConstructorResolution==false非宽松构造函数解析模式下执行该检查,// !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes()条件// 显式忽略重写的方法(具有相同的参数签名))。else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&!mbd.isLenientConstructorResolution() &&//如果当前typeDiffWeight和目前遍历出的候选方法相同,且参数个数相同paramTypes.length == factoryMethodToUse.getParameterCount() &&!Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {if (ambiguousFactoryMethods == null) {ambiguousFactoryMethods = new LinkedHashSet<>();ambiguousFactoryMethods.add(factoryMethodToUse);}ambiguousFactoryMethods.add(candidate);}}}if (factoryMethodToUse == null || argsToUse == null) {if (causes != null) {UnsatisfiedDependencyException ex = causes.removeLast();for (Exception cause : causes) {this.beanFactory.onSuppressedException(cause);}throw ex;}List<String> argTypes = new ArrayList<>(minNrOfArgs);if (explicitArgs != null) {for (Object arg : explicitArgs) {argTypes.add(arg != null ? arg.getClass().getSimpleName() : "null");}}else if (resolvedValues != null) {Set<ValueHolder> valueHolders = new LinkedHashSet<>(resolvedValues.getArgumentCount());//getIndexedArgumentValues()返回参数索引为键,ValueHolder为值的不可修改的MapvalueHolders.addAll(resolvedValues.getIndexedArgumentValues().values());valueHolders.addAll(resolvedValues.getGenericArgumentValues());//维护argTypes Listfor (ValueHolder value : valueHolders) {String argType = (value.getType() != null ? ClassUtils.getShortName(value.getType()) :(value.getValue() != null ? value.getValue().getClass().getSimpleName() : "null"));argTypes.add(argType);}}String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes);throw new BeanCreationException(mbd.getResourceDescription(), beanName,"No matching factory method found: " +(mbd.getFactoryBeanName() != null ?"factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +"factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " +"Check that a method with the specified name " +(minNrOfArgs > 0 ? "and arguments " : "") +"exists and that it is " +(isStatic ? "static" : "non-static") + ".");}else if (void.class == factoryMethodToUse.getReturnType()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Invalid factory method '" + mbd.getFactoryMethodName() +"': needs to have a non-void return type!");}else if (ambiguousFactoryMethods != null) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Ambiguous factory method matches found in bean '" + beanName + "' " +"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +ambiguousFactoryMethods);}//维护mbd和缓存if (explicitArgs == null && argsHolderToUse != null) {mbd.factoryMethodToIntrospect = factoryMethodToUse;argsHolderToUse.storeCache(mbd, factoryMethodToUse);}}//确定工厂方法后,开始实例化beanbw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));return bw;}

Sping中怎么处理@Bean注解bean同名的问题相关推荐

  1. SpringBoot中Configure注解和Bean注解的使用

    2019独角兽企业重金招聘Python工程师标准>>> Configure注解该类为配置类,Bean注解bean对象如下demo package com.yudian.springb ...

  2. Spring之-bean注解

    @Bean注解 @bean注解是Spring中的一个重要注解,主要是用在方法上,将该方法发返回值注册成一个Bean,并加入到Spring中进行管理. 使用方法 @Configuration publi ...

  3. AnnotationConfigUtils 处理注解Bean 定义类中的通用注解

    AnnotationConfigUtils 类的processCommonDefinitionAnnotations()在向容器注册Bean 之前,首先对注解Bean 定义类中的通用Spring 注解 ...

  4. 详细讲解Spring中的@Bean注解

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/weixin_42140261/ article/details/104864333 随着SpringBoot的流行,我们现在更 ...

  5. java getbean方法_java main方法中通过ApplicationContext去拿bean(用注解的bean)getbean拿不到?...

    java main方法中通过ApplicationContext去拿bean(用注解的bean)getbean拿不到? 注解类代码如下: ApplicatonContext.xml配置如下: main ...

  6. @Bean的方法中如何注入同一个@Configuration注解的类里的其他@Bean注解的bean

    在看别人代码的时候,发现一个问题,简单了个例子 @Configurationpublic class MyConfig{@Beanpublic Test1 test1() {return new Te ...

  7. Spring系列(二):Bean注解用法介绍

    今天给大家介绍一下Spring中Bean注解的用法,后续的文章给大家介绍Sping其他注解用法,希望对大家日常工作能有所帮助! 1.首先创建一个maven项目引入spring依赖 <depend ...

  8. spring注解--@Bean

    @Configuration public class KnightConfig {@Beanpublic Knight knight(){return new BraveKnight(quest ( ...

  9. 头条一面:Spring IOC容器中只存放单例Bean吗?

    最近,很多小伙伴出去面试,感觉自己面的不是很理想,回来后,不少小伙伴把面试题做了记录发给我,让我给大家解析下,然后发出来.当我看到这些面试题时,快速在脑海中构建起了整个知识体系,从基础到框架.从分布式 ...

最新文章

  1. 2018年HBase生态社群画像 +最全资料汇总下载
  2. 【django】HttpResponse对象
  3. 在Linux上安装JDK9
  4. Editplus 的配色方案
  5. 行车环境实时语义分割与深度估计
  6. java treemap排序_java – 使用TreeMap排序问题
  7. 国开网电大 动物常见病防治 形考任务1-5
  8. android安卓远程协助控制电脑PC端
  9. 3DMM(3D Morphable Model)原理和实现
  10. Squirrel中的类与实例
  11. 腾讯C++后台开发实习面经(已拿offer)
  12. poj2942点双连通奇圈-二分图判断Knights of the Round Table
  13. 老电脑安装XP时遇到问题的总结
  14. 组装台式计算机的流程,如何组装电脑?组装电脑的操作流程!
  15. image-conversion压缩图片
  16. 分布式系统一致性的发展历史 (二)
  17. 高薪必备!年薪80W+的阿里巴巴P8架构师都学习的笔记:《MySQL技术精粹》理论+实战齐飞
  18. 通达OA 前台任意用户登录漏洞getshell
  19. 阅读笔记:骑驴找马 职业发展线路
  20. 主机屋的免费PHP空间

热门文章

  1. 从“站在巨人的肩上”到“跪到侏儒之脚下”——图灵公司副主编自供(二)...
  2. python判断列表之间是否为包含关系,不用自定义函数
  3. 安卓9.0刷linux,Android 9.0 内核编译实战(以一加6为例)
  4. 解决Powershell前面没有base,无法激活虚拟环境问题
  5. 2015Astar百度之星初赛 1005 序列变化
  6. 三菱Plc怎么用c语言编程,如何用程序在三菱PLC上写出配方功能
  7. stm32 KEIL AC6 优化0程序不运行问题解决
  8. Python-docx实战:同事要我帮忙补写178份日报!别吧
  9. 王老六计算机,crc校验和累加和校验哪个好
  10. pip安装的依赖不在虚拟环境中