Sping中如何处理@Bean注解bean同名的问题
作者:Snowbae
首先明确
@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; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。