Sping中如何处理@Bean注解bean同名的问题

 更新时间:2023年06月20日 09:50:04   作者:Snowbae  
这篇文章主要介绍了Sping中如何处理@Bean注解bean同名的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

首先明确

@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同名的两种情况

  • @Bean注解的方法:方法名不同但是返回对象类型相同,@Bean注解中显式指定相同的beanName
  • @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,判断条件如下:

  • 首先是实例还是静态要和当前从bd中解析出的结果相同
  • mbd.isFactoryMethod(candidate)比较当前方法名和bd中保存的FactoryMethodName是否相同:

4.给所有candidate排序:

  • 先比较可见性public的排在前面
  • 否则比较参数个数,优先选参数多的

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中找是否有factoryBean
        if (factoryBeanName != null) {
            if (factoryBeanName.equals(beanName)) {//factoryBean不能和要创建的bean一样,即不能在配置类中配置config本身的bean
                throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
                        "factory-bean reference points back to the same bean definition");
            }
            factoryBean = this.beanFactory.getBean(factoryBeanName);//从bean工厂中返回factoryBean
            if (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);
                //遍历,找出所有符合要求的重载的candidate
                for (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) {
                    //工厂方法的参数个数为0
                    mbd.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);
            //寻找最小类型差异值minTypeDiffWeight
            int 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为值的不可修改的Map
                    valueHolders.addAll(resolvedValues.getIndexedArgumentValues().values());
                    valueHolders.addAll(resolvedValues.getGenericArgumentValues());
                    //维护argTypes List
                    for (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);
            }
        }
        //确定工厂方法后,开始实例化bean
        bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));
        return bw;
    }

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot详细讲解静态资源导入的实现

    SpringBoot详细讲解静态资源导入的实现

    在Web开发过程中,我们需要接触许多静态资源,如CSS、JS、图片等;在之前的开发中,这些资源都放在Web目录下,用到的时候按照对应路径访问即可。不过在SpringBoot项目中,没有了Web目录,那这些静态资源该放到哪里去,又要如何访问呢?这就是我们要讲的静态资源导入
    2022-05-05
  • 谈谈Java中Volatile关键字的理解

    谈谈Java中Volatile关键字的理解

    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果,本文给大家介绍java中volatile关键字,需要的朋友参考下
    2016-03-03
  • springboot项目中jackson-序列化-处理 NULL教程

    springboot项目中jackson-序列化-处理 NULL教程

    这篇文章主要介绍了springboot项目中jackson-序列化-处理 NULL教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • mybatis createcriteria和or的区别说明

    mybatis createcriteria和or的区别说明

    这篇文章主要介绍了mybatis createcriteria和or的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • java基于jcifs.smb实现远程发送文件到服务器

    java基于jcifs.smb实现远程发送文件到服务器

    这篇文章主要介绍了java基于jcifs.smb实现远程发送文件到服务器,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • java 自动生成略缩图示例代码

    java 自动生成略缩图示例代码

    本篇文章,在前辈的经验基础上,分别对单图生成略缩图和批量生成略缩图做个小结
    2013-07-07
  • struts2与cookie 实现自动登录和验证码验证实现代码

    struts2与cookie 实现自动登录和验证码验证实现代码

    这篇文章主要介绍了struts2与cookie 实现自动登录和验证码验证实现代码的相关资料,需要的朋友可以参考下
    2016-10-10
  • spring boot项目快速构建的全步骤

    spring boot项目快速构建的全步骤

    这篇文章主要给大家介绍了关于spring boot项目快速构建的全步骤,文中通过示例代码介绍的非常详细,对大家学习或者使用spring boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-09-09
  • Java的PriorityBlockingQueue优先级阻塞队列代码实例

    Java的PriorityBlockingQueue优先级阻塞队列代码实例

    这篇文章主要介绍了Java的PriorityBlockingQueue优先级阻塞队列代码实例,PriorityBlockingQueue顾名思义是带有优先级的阻塞队列,为了实现按优先级弹出数据,存入其中的对象必须实现comparable接口自定义排序方法,需要的朋友可以参考下
    2023-12-12
  • Springboot 如何使用BindingResult校验参数

    Springboot 如何使用BindingResult校验参数

    这篇文章主要介绍了Springboot 如何使用BindingResult校验参数,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论