Spring推断构造方法(下)

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。Spring推断构造方法(下),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、前言

Spring推断构造方法的前面两篇文章已经对查找候选构造方法以及推断使用的构造方法做了详细的介绍,在最后,这边文章主要简单介绍一下@Bean的情况

首先Spring在启动的过程中,会把@Bean修饰的方法解析成BeanDefinition

如果方法不是static的,BeanDefinition的参数如下:

  • factoryBeanName为@Bean方法所在类的beanName,比如下面的例子中factoryBeanName就是”appConfig”

    @ComponentScan("com.lizhi")
    public class AppConfig {
    
    	@Bean
    	public UserService userService(){
    		return new UserService();
    	}
    }
    
  • factoryMethodName为对应的方法名,比如”userService”

  • beanClass为AppConfig.class

如果方法是static的,BeanDefinition的参数如下:

  • factoryBeanName为null
  • factoryMethodName为对应的方法名,比如”userService”
  • beanClass也为AppConfig.class

在由@Bean生成的BeanDefinition中,有一个重要的属性isFactoryMethodUnique,表示factoryMethod是不是唯一的,在普通情况下@Bean生成的BeanDefinition的isFactoryMethodUnique为true,如果出现了方法重载,或者其他类也有同样的方法,那就是特殊情况,比如:

@Bean
public static AService aService(){
    return new AService();
}

@Bean
public AService aService(BService bService){
    return new AService();
}
@ComponentScan("com.lizhi")
public class AppConfig {

   @Bean
   public UserService userService(){
      return new UserService();
   }
}

@Configuration
public class GlobalConfig {

   @Bean
   public UserService userService(){
      return new UserService();
   }
}

上面的这两种情况,都只会生成一个BeanDefinition,但是BeanDefinition的属性isFactoryMethodUnique为false

下面将介绍@Bean如果存在多个方法时,如何进行推断构造的

二、推断@Bean的实例方法

在createBeanInstance()方法中,前面会去判断当前创建的Bean实例是否是通过@Bean的方法创建的,如果是通过这种方式,将推断出factoryMethod来创建实例

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
   // Make sure bean class is actually resolved at this point.
   Class<?> beanClass = resolveBeanClass(mbd, beanName);
   ……
   // @Bean对应的BeanDefinition
   if (mbd.getFactoryMethodName() != null) {
      return instantiateUsingFactoryMethod(beanName, mbd, args);
   }
}

2.1 判断方法是否是static并获取beanClass

通过BeanDefinition的factoryBeanName是否为空来区分@Bean的方法是否是Static的

如果@Bean的实例已经创建了,则直接抛异常

否则缓存@Bean实例的beanName与所在类实例的beanName的依赖关系,并获取beanClass

isStatic变量记录了方法的类型是否为static,后面筛选@Bean方法时会用到

public BeanWrapper instantiateUsingFactoryMethod(
      String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {

   BeanWrapperImpl bw = new BeanWrapperImpl();
   this.beanFactory.initBeanWrapper(bw);

   Object factoryBean;
   Class<?> factoryClass;
   boolean isStatic;

   // 注意,这里拿到的是factoryBeanName,而不是factoryMethodName,比如AppConfig对象
   String factoryBeanName = mbd.getFactoryBeanName();
   if (factoryBeanName != null) {
      if (factoryBeanName.equals(beanName)) {
         throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
               "factory-bean reference points back to the same bean definition");
      }
      factoryBean = this.beanFactory.getBean(factoryBeanName);

      // 该单例已经创建好了?
      if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
         throw new ImplicitlyAppearedSingletonException();
      }
      this.beanFactory.registerDependentBean(factoryBeanName, beanName);
      factoryClass = factoryBean.getClass();
      isStatic = false;
   }
   else {
      // It's a static factory method on the bean class.
      // static的@Bean方法
      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();
      isStatic = true;
   }
    ……
}

后面的操作同普通的推断构造的代码一致,会判断getBean()是否指定了参数值以及是否缓存了factoryMethod和方法参数值

2.2 寻找@Bean方法

isFactoryMethodUnique为true,如果只有一个方法,前面从缓存中可能获取了,如果缓存中没有,则直接从BeanDefinition中得到可用factoryMethod

其中candidates变量记录了缓存中的factoryMethod,如果缓存中没有,则去遍历factoryClass(@Beans所在类)的所有方法,然后筛选出与前面isStatic类型相同且与BeanDefinition中factoryMethod相同的方法,然后加入到候选的方法数组candidates中

List<Method> candidates = null;
if (mbd.isFactoryMethodUnique) {
   if (factoryMethodToUse == null) {
      factoryMethodToUse = mbd.getResolvedFactoryMethod();
   }
   if (factoryMethodToUse != null) {
      candidates = Collections.singletonList(factoryMethodToUse);
   }
}

// 找到对应的@Bean方法,由于可能参数重载,所以有可能会有多个
if (candidates == null) {
   candidates = new ArrayList<>();
   Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
   for (Method candidate : rawCandidates) {
      if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
         candidates.add(candidate);
      }
   }
}

2.3 使用默认方法

如果只有一个候选方法,并且getBean()调用没有指定参数值,BeanDefinition也没有指定参数值,且方法参数个数为0,那么就使用这个方法进行实例化,并将这个方法进行缓存

if (candidates.size() == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
   Method uniqueCandidate = candidates.get(0);
   if (uniqueCandidate.getParameterCount() == 0) {
      mbd.factoryMethodToIntrospect = uniqueCandidate;
      synchronized (mbd.constructorArgumentLock) {
         mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
         mbd.constructorArgumentsResolved = true;
         mbd.resolvedConstructorArguments = EMPTY_ARGS;
      }
      bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, uniqueCandidate, EMPTY_ARGS));
      return bw;
   }
}

如果不满足上面的条件,将对候选的方法进行排序,排序规则与构造推断一样,按public、参数个数进行排序

2.4 记录最小参数个数

该逻辑与构造推断的逻辑基本一致

int minNrOfArgs;
if (explicitArgs != null) {
   minNrOfArgs = explicitArgs.length;
}
else {
   // 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()) {
      ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
      resolvedValues = new ConstructorArgumentValues();
      minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
   }
   else {
      minNrOfArgs = 0;
   }
}

2.5 遍历候选方法,推断合适的候选方法

这段的逻辑与构造方法推断也是一样的,先为每个方法寻找参数值,如果getBean()调用或者BeanDefinition指定了参数值,就用指定的,如果没有指定,并且autowiring指定可以通过Spring对其进行注入时,Spring会根据类型、参数名称去查找可用的Bean实例

boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
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.
        // 根据参数类型和参数名找Bean
        String[] paramNames = null;
        ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
        if (pnd != null) {
            paramNames = pnd.getParameterNames(candidate);
        }
        argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,
                                         paramTypes, paramNames, candidate, autowiring, candidates.size() == 1);
    }

为方法找完参数之后,将会方法进行打分,逻辑与构造方法的打分是一样的,对于方法的分数相同的、参数个数相同、类型相同的方法放入到ambiguousFactoryMethods中缓存

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;
}
// Find out about ambiguity: In case of the same type difference weight
// for methods with the same number of parameters, collect such candidates
// and eventually raise an ambiguity exception.
// However, only perform that check in non-lenient constructor resolution mode,
// and explicitly ignore overridden methods (with the same parameter signature).
else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
      !mbd.isLenientConstructorResolution() &&
      paramTypes.length == factoryMethodToUse.getParameterCount() &&
      !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
   if (ambiguousFactoryMethods == null) {
      ambiguousFactoryMethods = new LinkedHashSet<>();
      ambiguousFactoryMethods.add(factoryMethodToUse);
   }
   ambiguousFactoryMethods.add(candidate);
}

如果找到多个可用的,或者找的一个方法的返回值类型是Void类型,都会抛出异常

如果只找到一个可用的方法,并且不是通过getBean()调用传入的参数,且对方法和参数进行缓存,然后利用该方法和参数生成@Bean实例

// 方法返回值类型为Void
if (void.class == factoryMethodToUse.getReturnType()) {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" +
                                    factoryClass.getName() + "]: needs to have a non-void return type!");
}
// 找到多个可用的方法
else if (ambiguousFactoryMethods != null) {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Ambiguous factory method matches found on class [" + factoryClass.getName() + "] " +
                                    "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
                                    ambiguousFactoryMethods);
}

if (explicitArgs == null && argsHolderToUse != null) {
    mbd.factoryMethodToIntrospect = factoryMethodToUse;
    argsHolderToUse.storeCache(mbd, factoryMethodToUse);
}
}

bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));
return bw;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/153661.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!