谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?(2)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?(2)

》通过bd中的supplier属性实例化对象


在Spring官网阅读(一)容器及实例化 文中介绍过这种方式,因为这种方式我们基本不会使用,并不重要,所以这里就不再赘述,我这里就直接给出一个使用示例,大家自行体会吧

public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 直接注册一个Bean,并且指定它的supplier就是Service::new
    ac.registerBean("service", Service.class,Service::new,zhe'sh);
    ac.refresh();
    System.out.println(ac.getBean("service"));
}

》通过bd中的factoryMethodName跟factoryBeanName实例化对象


对应代码如下:

protected BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
}

上面这段代码主要干了两件事


  • 创建一个ConstructorResolver对象,从类名来看,它是一个构造器解析器
  • 调用了这个构造器解析器的instantiateUsingFactoryMethod方法,这个方法见名知意,使用FactoryMethod来完成实例化

基于此,我们解决一个问题,ConstructorResolver是什么?


ConstructorResolver是什么?


在要研究一个类前,我们最先应该从哪里入手呢?很多没有经验的同学可能会闷头看代码,但是实际上最好的学习方式是先阅读类上的javaDoc


ConstructorResolver上的javaDoc如下:

微信图片_20221113170241.png

上面这段javaDoc翻译过来就是这个类就是用来解析构造函数跟工厂方法的代理者,并且它是通过参数匹配的方式来进行推断构造方法或者工厂方法。


看到这里不知道小伙伴们是否有疑问,就是明明这个类不仅负责推断构造函数,还会负责推断工厂方法,那么为什么类名会叫做ConstructorResolver呢?我们知道Spring的代码在业界来说绝对是最规范的,没有之一,这样来说的话,这个类最合适的名称应该是ConstructorAndFactoryMethodResolver才对,因为它不仅负责推断了构造函数还负责推断了工厂方法嘛!


这里我需要说一下我自己的理解。*对于一个Bean,它是通过构造函数完成实例化的,或者通过工厂方法实例化的,其实在这个Bean看来都没有太大区别,这两者都可以称之为这个Bean的构造器,因为通过它们都能构造出一个Bean。*所以Spring就把两者统称为构造器了,所以这个类名也就被称为ConstructorResolver了。


Spring在很多地方体现了这种实现,例如在XML配置的情况下,不论我们是使用构造函数创建对象还是使用工厂方法创建对象,其参数的标签都是使用constructor-arg。比如下面这个例子

<bean id="dmzServiceGetFromStaticMethod"
      factory-bean="factoryBean"
      factory-method="getObject">
    <constructor-arg type="java.lang.String" value="hello" name="s"/>
    <constructor-arg type="com.dmz.source.instantiation.service.DmzFactory" ref="factoryBean"/>
</bean>
<!--测试静态工厂方法创建对象-->
<bean id="service"
      class="com.dmz.official.service.MyFactoryBean"
      factory-method="staticGet">
    <constructor-arg type="java.lang.String" value="hello"/>
</bean>
<bean id="dmzService" class="com.dmz.source.instantiation.service.DmzService">
    <constructor-arg name="s" value="hello"/>
</bean>

在对这个类有了大概的了解后,我们就需要来分析它的源码,这里我就不把它单独拎出来分析了,我们借着Spring的流程看看这个类干了什么事情


instantiateUsingFactoryMethod方法做了什么?


核心目的:推断出要使用的factoryMethod以及调用这个FactoryMethod要使用的参数,然后反射调用这个方法实例化出一个对象

这个方法的代码太长了,所以我们将它拆分成为一段一段的来分析


方法参数分析


在分析上面的代码之前,我们先来看看这个方法的参数都是什么含义


方法上关于参数的介绍如图所示

微信图片_20221113170451.png


  • beanName:当前要实例化的Bean的名称
  • mbd:当前要实例化的Bean对应的BeanDefinition
  • explicitArgs:这个参数在容器启动阶段我们可以认定它就是null,只有显示的调用了getBean方法,并且传入了明确的参数,例如:getBean("dmzService","hello")这种情况下才会不为null,我们分析这个方法的时候就直接认定这个参数为null即可。


第一段


public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    // 第一段代码:创建并初始话一个BeanWrapperImpl
    BeanWrapperImpl bw = new BeanWrapperImpl();
    this.beanFactory.initBeanWrapper(bw);
    // ......
}


BeanWrapperImpl是什么呢?如果你看过我之前的文章:Spring官网阅读(十四)Spring中的BeanWrapper及类型转换,那么你对这个类应该不会陌生,它就是对Bean进行了一层包装,并且在创建Bean的时候以及进行属性注入的时候能够进行类型转换。就算你没看过之前的文章也没关系,只要记住两点


  • BeanWrapperImpl包装了一个实例化好的对象
  • BeanWrapperImpl能够对属性进行类型转换

其层级关系如下:

微信图片_20221113170559.png

回到我们的源码分析,我们先来看看new BeanWrapperImpl()做了什么事情?

对应代码如下:

// 第一步:调用空参构造
public BeanWrapperImpl() {
    // 调用另外一个构造函数,表示要注册默认的属性编辑器
    this(true);
}
// 这个构造函数表明是否要注册默认编辑器,上面传入的值为true,表示需要注册
public BeanWrapperImpl(boolean registerDefaultEditors) {
    super(registerDefaultEditors);
}
// 调用到父类的构造函数,确定要使用默认的属性编辑器
protected AbstractNestablePropertyAccessor(boolean registerDefaultEditors) {
    if (registerDefaultEditors) {
        registerDefaultEditors();
    }
    // 对typeConverterDelegate进行初始化
    this.typeConverterDelegate = new TypeConverterDelegate(this);
}

总的来说创建的过程非常简单。第一,确定要注册默认的属性编辑器;第二,对typeConverterDelegate属性进行初始化。

紧接着,我们看看在初始化这个BeanWrapper做了什么?

// 初始化BeanWrapper,主要就是将容器中配置的conversionService赋值到当前这个BeanWrapper上
// 同时注册定制的属性编辑器
protected void initBeanWrapper(BeanWrapper bw) {
    bw.setConversionService(getConversionService());
    registerCustomEditors(bw);
}

还记得conversionService在什么时候被放到容器中的吗?就是在finishBeanFactoryInitialization的时候啦~!

对conversionService属性完成赋值后就开始注册定制的属性编辑器,代码如下:

// 传入的参数就是我们的BeanWrapper,它同时也是一个属性编辑器注册表
protected void registerCustomEditors(PropertyEditorRegistry registry) {
    PropertyEditorRegistrySupport registrySupport =
        (registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null);
    if (registrySupport != null) {
        // 这个配置的作用就是在注册默认的属性编辑器时,可以增加对数组到字符串的转换功能
        // 默认就是通过","来切割字符串转换成数组,对应的属性编辑器就是StringArrayPropertyEditor
        registrySupport.useConfigValueEditors();
    }
    // 将容器中的属性编辑器注册到当前的这个BeanWrapper
    if (!this.propertyEditorRegistrars.isEmpty()) {
        for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
            registrar.registerCustomEditors(registry);
            // 省略异常处理~
        }
    }
    // 这里我们没有添加任何的自定义的属性编辑器,所以肯定为空
    if (!this.customEditors.isEmpty()) {
        this.customEditors.forEach((requiredType, editorClass) ->
                                   registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
    }
}

第二段


public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    // 省略已经分析的第一段代码,到这里已经得到了一个具有类型转换功能的BeanWrapper
    // 实例化这个Bean的工厂Bean
    Object factoryBean;
    // 工厂Bean的Class
    Class<?> factoryClass;
    // 静态工厂方法或者是实例化工厂方法
    boolean isStatic;
    /*下面这段代码就是为上面申明的这三个属性赋值*/ 
    String factoryBeanName = mbd.getFactoryBeanName();
    // 如果创建这个Bean的工厂就是这个Bean本身的话,那么直接抛出异常
    if (factoryBeanName != null) {
        if (factoryBeanName.equals(beanName)) {
            throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
                                                   "factory-bean reference points back to the same bean definition");
        }
        // 得到创建这个Bean的工厂Bean
        factoryBean = this.beanFactory.getBean(factoryBeanName);
        if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
            throw new ImplicitlyAppearedSingletonException();
        }
        factoryClass = factoryBean.getClass();
        isStatic = false;
    }
    else {
        // factoryBeanName为null,说明是通过静态工厂方法来实例化Bean的
        // 静态工厂进行实例化Bean,beanClass属性必须要是工厂的class,如果为空,直接报错
        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;
    }
    // 省略后续代码
}

小总结:

这段代码很简单,就是确认实例化当前这个Bean的工厂方法是静态工厂还是实例工厂,如果是实例工厂,那么找出对应的工厂Bean。


第三段


public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    // 省略第一段,第二段代码
    // 到这里已经得到了一个BeanWrapper,明确了实例化当前这个Bean到底是静态工厂还是实例工厂
    // 并且已经确定了工厂Bean
    // 最终确定的要用来创建对象的方法
    Method factoryMethodToUse = null;
    ArgumentsHolder argsHolderToUse = null;
    Object[] argsToUse = null;
    // 参数分析时已经说过,explicitArgs就是null
    if (explicitArgs != null) {
        argsToUse = explicitArgs;
    }
    else {
        // 下面这段代码是什么意思呢?
        // 在原型模式下,我们会多次创建一个Bean,所以Spring对参数以及所使用的方法做了缓存
        // 在第二次创建原型对象的时候会进入这段缓存的逻辑
        // 但是这里有个问题,为什么Spring对参数有两个缓存呢?
        // 一:resolvedConstructorArguments
        // 二:preparedConstructorArguments
        // 这里主要是因为,直接使用解析好的构造的参数,因为这样会导致创建出来的所有Bean都引用同一个属性
        Object[] argsToResolve = null;
        synchronized (mbd.constructorArgumentLock) {
            factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod;
            // 缓存已经解析过的工厂方法或者构造方法
            if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) {
                // resolvedConstructorArguments跟preparedConstructorArguments都是对参数的缓存
                argsToUse = mbd.resolvedConstructorArguments;
                if (argsToUse == null) {
                    argsToResolve = mbd.preparedConstructorArguments;
                }
            }
        }
        if (argsToResolve != null) {
            // preparedConstructorArguments需要再次进行解析
            argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);
        }
    }
    // 省略后续代码
}

小总结:


上面这段代码应该没什么大问题,其核心思想就是从缓存中取已经解析出来的方法以及参数,这段代码只会在原型模式下生效,因为单例的话对象只会创建一次嘛~!最大的问题在于,为什么在对参数进行缓存的时候使用了两个不同的集合,并且缓存后的参数还需要再次解析,这个问题我们暂且放着,不妨带着这个问题往下看。


因为接下来要分析的代码就比较复杂了,所以为了让你彻底看到代码的执行流程,下面我会使用示例+流程图+文字的方式来分析源码。


示例代码如下(这个例子覆盖接下来要分析的所有流程):


配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
     default-autowire="constructor"><!--这里开启自动注入,并且是通过构造函数进行自动注入-->
  <!--factoryObject 提供了创建对象的方法-->
  <bean id="factoryObject" class="com.dmz.spring.first.instantiation.service.FactoryObject"/>
  <!--提供一个用于测试自动注入的对象-->
  <bean class="com.dmz.spring.first.instantiation.service.OrderService" id="orderService"/>
    <!--主要测试这个对象的实例化过程-->
  <bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" scope="prototype">
    <constructor-arg name="name" value="dmz"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="birthDay" value="2020-05-23"/>
  </bean>
    <!--测试静态方法实例化对象的过程-->
  <bean id="indexService" class="com.dmz.spring.first.instantiation.service.FactoryObject"
      factory-method="staticGetIndex"/>
    <!--提供这个转换器,用于转换dmzService中的birthDay属性,从字符串转换成日期对象-->
  <bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
    <property name="converters">
      <set>
        <bean class="com.dmz.spring.first.instantiation.service.ConverterStr2Date"/>
      </set>
    </property>
  </bean>
</beans>

测试代码:

public class FactoryObject {
  public DmzService getDmz(String name, int age, Date birthDay, OrderService orderService) {
    System.out.println("getDmz with "+"name,age,birthDay and orderService");
    return new DmzService();
  }
  public DmzService getDmz(String name, int age, Date birthDay) {
    System.out.println("getDmz with "+"name,age,birthDay");
    return new DmzService();
  }
  public DmzService getDmz(String name, int age) {
    System.out.println("getDmz with "+"name,age");
    return new DmzService();
  }
  public DmzService getDmz() {
    System.out.println("getDmz with empty arg");
    return new DmzService();
  }
  public static IndexService staticGetIndex() {
    return new IndexService();
  }
}
public class DmzService {
}
public class IndexService {
}
public class OrderService {
}
public class ConverterStr2Date implements Converter<String, Date> {
  @Override
  public Date convert(String source) {
    try {
      return new SimpleDateFormat("yyyy-MM-dd").parse(source);
    } catch (ParseException e) {
      return null;
    }
  }
}
/**
 * @author 程序员DMZ
 * @Date Create in 23:14 2020/5/21
 * @Blog https://daimingzhi.blog.csdn.net/
 */
public class Main {
  public static void main(String[] args) {
    ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext();
    cc.setConfigLocation("application.xml");
    cc.refresh();
    cc.getBean("dmzService");
        // 两次调用,用于测试缓存的方法及参数
//    cc.getBean("dmzService");
  }
}

运行上面的代码会发现,程序打印:

getDmz with name,age,birthDay and orderService

具体原因我相信你看了接下来的源码分析自然就懂了


第四段


public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
//  第一段代码:到这里已经得到了一个BeanWrapper,并对这个BeanWrapper做了初始化
//  第二段代码:明确了实例化当前这个Bean到底是静态工厂还是实例工厂
//  第三段代码:以及从缓存中取过了对应了方法以及参数
// 进入第四段代码分析,执行到这段代码说明是第一次实例化这个对象
if (factoryMethodToUse == null || argsToUse == null) {
      // 如果被cglib代理的话,获取父类的class
      factoryClass = ClassUtils.getUserClass(factoryClass);
      // 获取到工厂类中的所有方法,接下来要一步步从这些方法中筛选出来符合要求的方法
      Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
      List<Method> candidateList = new ArrayList<>();
        // 第一步筛选:之前 在第二段代码中已经推断了方法是静态或者非静态的
        // 所以这里第一个要求就是要满足静态/非静态这个条件
        // 第二个要求就是必须符合bd中定义的factoryMethodName的名称
        // 其中第二个要求请注意,如果bd是一个configurationClassBeanDefinition,也就是说是通过扫描@Bean注解产生的,那么在判断时还会添加是否标注了@Bean注解
      for (Method candidate : rawCandidates) {
        if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
          candidateList.add(candidate);
        }
      }
        // 将之前得到的方法集合转换成数组
        // 到这一步得到的其实就是某一个方法的所有重载方法
        // 比如dmz(),dmz(String name),dmz(String name,int age)
      Method[] candidates = candidateList.toArray(new Method[0]);
        // 排序,public跟参数多的优先级越高
      AutowireUtils.sortFactoryMethods(candidates);
        // 用来保存从配置文件中解析出来的参数
      ConstructorArgumentValues resolvedValues = null;
            // 是否使用了自动注入,本段代码中没有使用到这个属性,但是在后面用到了
      boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
      int minTypeDiffWeight = Integer.MAX_VALUE;
        // 可能出现多个符合要求的方法,用这个集合保存,实际上如果这个集合有值,就会抛出异常了
      Set<Method> ambiguousFactoryMethods = null;
      int minNrOfArgs;
        // 必定为null,不考虑了
      if (explicitArgs != null) {
        minNrOfArgs = explicitArgs.length;
      }
      else {
                // 就是说配置文件中指定了要使用的参数,那么需要对其进行解析,解析后的值就存储在resolvedValues这个集合中
        if (mbd.hasConstructorArgumentValues()) {
                    // 通过解析constructor-arg标签,将参数封装成了ConstructorArgumentValues
                    // ConstructorArgumentValues这个类在下文我们专门分析
          ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
          resolvedValues = new ConstructorArgumentValues();
                    // 解析标签中的属性,类似进行类型转换,后文进行详细分析
          minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
        }
        else {
                    // 配置文件中没有指定要使用的参数,所以执行方法的最小参数个数就是0
          minNrOfArgs = 0;
        }
      }
  // 省略后续代码....
}

小总结:


因为在实例化对象前必定要先确定具体要使用的方法,所以这里先做的第一件事就是确定要在哪个范围内去推断要使用的factoryMethod呢?

最大的范围就是这个factoryClass的所有方法,也就是源码中的rawCandidates

其次需要在rawCandidates中进一步做推断,因为在前面第二段代码的时候已经确定了是静态方法还是非静态方法,并且BeanDefinition也指定了factoryMethodName,那么基于这两个条件这里就需要对rawCandidates进一步进行筛选,得到一个candidateList集合。

我们对示例的代码进行调试会发现

微信图片_20221113171004.png

确实如我们所料,rawCandidates是factoryClass中的所有方法,candidateList是所有getDmz的重载方法。


在确定了推断factoryMethod的范围后,那么接下来要根据什么去确定到底使用哪个方法呢?换个问题,怎么区分这么些重载的方法呢?肯定是根据方法参数嘛!


所以接下来要做的就是去解析要使用的参数了~

对于Spring而言,方法的参数会分为两种

1.配置文件中指定的

2.自动注入模式下,需要去容器中查找的

在上面的代码中,Spring就是将配置文件中指定的参数做了一次解析,对应方法就是resolveConstructorArguments。


在查看这个方法的源码前,我们先看看ConstructorArgumentValues这个类

public class ConstructorArgumentValues {
  // 通过下标方式指定的参数
  private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<>();
  // 没有指定下标
  private final List<ValueHolder> genericArgumentValues = new ArrayList<>();
  // 省略无关代码.....
}

在前文的注释中我们也说过了,它主要的作用就是封装解析constructor-arg标签得到的属性,解析标签对应的方法就是org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseConstructorArgElement,这个方法我就不带大家看了,有兴趣的可以自行阅读。


它主要有两个属性

1.indexedArgumentValues

2.genericArgumentValues

对应的就是我们两种指定参数的方法,如下:

<bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" scope="prototype">
    <constructor-arg name="name" value="dmz"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg index="2"  value="2020-05-23"/>
    <!--  <constructor-arg name="birthDay" value="2020-05-23"/>-->
</bean>


/

其中的name跟age属性会被解析为genericArgumentValues,而index=2会被解析为indexedArgumentValues。

在对ConstructorArgumentValues有一定认知之后,我们再来看看resolveConstructorArguments的代码:

// 方法目的:解析配置文件中指定的方法参数
// beanName:bean名称
// mbd:beanName对应的beanDefinition
// bw:通过它进行类型转换
// ConstructorArgumentValues cargs:解析标签得到的属性,还没有经过解析(类型转换)
// ConstructorArgumentValues resolvedValues:已经经过解析的参数
// 返回值:返回方法需要的最小参数个数
private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,
                                        ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) {
    // 是否有定制的类型转换器,没有的话直接使用BeanWrapper进行类型转换
    TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
    TypeConverter converter = (customConverter != null ? customConverter : bw);
    // 构造一个BeanDefinitionValueResolver,专门用于解析constructor-arg中的value属性,实际上还包括ref属性,内嵌bean标签等等
    BeanDefinitionValueResolver valueResolver =
        new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter);
    // minNrOfArgs 记录执行方法要求的最小参数个数,一般情况下就是等于constructor-arg标签指定的参数数量
    int minNrOfArgs = cargs.getArgumentCount();
    for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : cargs.getIndexedArgumentValues().entrySet()) {
        int index = entry.getKey();
        if (index < 0) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                            "Invalid constructor argument index: " + index);
        }
        // 这是啥意思呢?
        // 这个代码我认为是有问题的,并且我给Spring官方已经提了一个issue,官方将会在5.2.7版本中修复
        // 暂且你先这样理解
        // 假设A方法直接在配置文件中指定了index=3上要使用的参数,那么这个时候A方法至少需要4个参数
        // 但是其余的3个参数可能不是通过constructor-arg标签指定的,而是直接自动注入进来的,那么在配置文件中我们就只配置了index=3上的参数,也就是说 int minNrOfArgs = cargs.getArgumentCount()=1,这个时候 index=3,minNrOfArgs=1, 所以 minNrOfArgs = 3+1
        if (index > minNrOfArgs) {
            minNrOfArgs = index + 1;
        }
        ConstructorArgumentValues.ValueHolder valueHolder = entry.getValue();
        // 如果已经转换过了,直接添加到resolvedValues集合中
        if (valueHolder.isConverted()) {
            resolvedValues.addIndexedArgumentValue(index, valueHolder);
        }
        else {
            // 解析value/ref/内嵌bean标签等
            Object resolvedValue =
                valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());
            // 将解析后的resolvedValue封装成一个新的ValueHolder,并将其source设置为解析constructor-arg得到的那个ValueHolder,后期会用到这个属性进行判断
            ConstructorArgumentValues.ValueHolder resolvedValueHolder =
                new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName());
            resolvedValueHolder.setSource(valueHolder);
            resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder);
        }
    }
   // 对getGenericArgumentValues进行解析,代码基本一样,不再赘述
    return minNrOfArgs;
}

可以看到,最终的解析逻辑就在resolveValueIfNecessary这个方法中,那么这个方法又做了什么呢?

// 这个方法的目的就是将解析constructor-arg标签得到的value值进行一次解析
// 在解析标签时ref属性会被封装为RuntimeBeanReference,那么在这里进行解析时就会去调用getBean
// 在解析value属性会会被封装为TypedStringValue,那么这里会尝试去进行一个转换
// 关于标签的解析大家有兴趣的话可以去看看org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyValue
// 这里不再赘述了
public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
    // 解析constructor-arg标签中的ref属性,实际就是调用了getBean
    if (value instanceof RuntimeBeanReference) {
        RuntimeBeanReference ref = (RuntimeBeanReference) value;
        return resolveReference(argName, ref);
    }
    // ......
       /**  
     * <constructor-arg>
     *      <set value-type="java.lang.String">
     *        <value>1</value>
     *      </set>
     * </constructor-arg>
     * 通过上面set标签中的value-type属性对value进行类型转换,
     * 如果value-type属性为空,那么这里不会进行类型转换
     */
   else if (value instanceof TypedStringValue) {
      TypedStringValue typedStringValue = (TypedStringValue) value;
      Object valueObject = evaluate(typedStringValue);
      try {
        Class<?> resolvedTargetType = resolveTargetType(typedStringValue);
        if (resolvedTargetType != null) {
          return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);
        }
        else {
          return valueObject;
        }
      }
      catch (Throwable ex) {
        // Improve the message by showing the context.
        throw new BeanCreationException(
            this.beanDefinition.getResourceDescription(), this.beanName,
            "Error converting typed String value for " + argName, ex);
      }
    }
    // 省略后续代码....
}

就我们上面的例子而言,经过resolveValueIfNecessary方法并不能产生实际的影响,因为在XML中我们没有配置ref属性或者value-type属性。

画图如下:

微信图片_20221113171352.png



/

第五段


public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    //  第一段代码:到这里已经得到了一个BeanWrapper,并对这个BeanWrapper做了初始化
    //  第二段代码:明确了实例化当前这个Bean到底是静态工厂还是实例工厂
    //  第三段代码:以及从缓存中取过了对应了方法以及参数
    //  第四段代码:明确了方法需要的最小的参数数量并对配置文件中的标签属性进行了一次解析
    // 进入第五段代码分析
    // 保存在创建方法参数数组过程中发生的异常,如果最终没有找到合适的方法,那么将这个异常信息封装后抛出
    LinkedList<UnsatisfiedDependencyException> causes = null;
    // 开始遍历所有在第四段代码中查询到的符合要求的方法
    for (Method candidate : candidates) {
        // 方法的参数类型
        Class<?>[] paramTypes = candidate.getParameterTypes();
        // 候选的方法的参数必须要大于在第四段这推断出来的最小参数个数
        if (paramTypes.length >= minNrOfArgs) {
            ArgumentsHolder argsHolder;
            // 必定为null,不考虑
            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);
                    }
                    // 根据方法的参数名称以及配置文件中配置的参数创建一个参数数组用于执行工厂方法
                    argsHolder = createArgumentArray(
                        beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring);
                }
                // 在创建参数数组的时候可能发生异常,这个时候的异常不能直接抛出,要确保所有的候选方法遍历完成,只要有一个方法符合要求即可,但是如果遍历完所有方法还是没找到合适的构造器,那么直接抛出这些异常
                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;
                }
                // 计算类型差异
                // 首先判断bd中是宽松模式还是严格模式,目前看来只有@Bean标注的方法解析得到的Bean会使用严格模式来计算类型差异,其余都是使用宽松模式
                // 严格模式下,
                int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                                      argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
                // 选择一个类型差异最小的方法
                if (typeDiffWeight < minTypeDiffWeight) {
                    factoryMethodToUse = candidate;
                    argsHolderToUse = argsHolder;
                    argsToUse = argsHolder.arguments;
                    minTypeDiffWeight = typeDiffWeight;
                    ambiguousFactoryMethods = null;
                }
    // 省略后续代码.......
    }

小总结:这段代码的核心思想就是根据第四段代码从配置文件中解析出来的参数构造方法执行所需要的实际参数数组。如果构建成功就代表这个方法可以用于实例化Bean,然后计算实际使用的参数跟方法上申明的参数的”差异值“,并在所有符合要求的方法中选择一个差异值最小的方法


接下来,我们来分析方法实现的细节


1.构建方法使用的参数数组,也就是createArgumentArray方法,其源码如下:

/* beanName:要实例化的Bean的名称
 * mbd:对应Bean的BeanDefinition
 * resolvedValues:从配置文件中解析出来的并尝试过类型转换的参数
 * bw:在这里主要就是用作类型转换器
 * paramTypes:当前遍历到的候选的方法的参数类型数组
 * paramNames:当前遍历到的候选的方法的参数名称
 * executable:当前遍历到的候选的方法
 * autowiring:是否时自动注入
 */
private ArgumentsHolder createArgumentArray(
    String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
      BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
      boolean autowiring) throws UnsatisfiedDependencyException {
    TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
    TypeConverter converter = (customConverter != null ? customConverter : bw);
    ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
    Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
    Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
    // 遍历候选方法的参数,跟据方法实际需要的类型到resolvedValues中去匹配
    for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
      Class<?> paramType = paramTypes[paramIndex];
      String paramName = (paramNames != null ? paramNames[paramIndex] : "");
      ConstructorArgumentValues.ValueHolder valueHolder = null;
      if (resolvedValues != null) {
                // 首先,根据方法参数的下标到resolvedValues中找对应的下标的属性
                // 如果没找到再根据方法的参数名/类型去resolvedValues查找
        valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
        // 如果都没找到
                // 1.是自动注入并且方法的参数长度正好跟配置中的参数数量相等
                // 2.不是自动注入
                // 那么按照顺序一次选取
        if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) {
          valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);
        }
      }
      // 也就是说在配置的参数中找到了合适的值可以应用于这个方法上
      if (valueHolder != null) {
        // 防止同一个参数被应用了多次
        usedValueHolders.add(valueHolder);
        Object originalValue = valueHolder.getValue();
        Object convertedValue;
                // 已经进行过类型转换就不会需要再次进行类型转换
        if (valueHolder.isConverted()) {
          convertedValue = valueHolder.getConvertedValue();
          args.preparedArguments[paramIndex] = convertedValue;
        }
        else {
          // 尝试将配置的值转换成方法参数需要的类型
          MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
          try {
                        // 进行类型转换
            convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
          }
          catch (TypeMismatchException ex) {
            // 抛出UnsatisfiedDependencyException,在调用该方法处会被捕获
          }
          Object sourceHolder = valueHolder.getSource();
                    // 只要是valueHolder存在,到这里这个判断必定成立
          if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) {
            Object sourceValue = ((ConstructorArgumentValues.ValueHolder) sourceHolder).getValue();
            args.resolveNecessary = true;
            args.preparedArguments[paramIndex] = sourceValue;
          }
        }
        args.arguments[paramIndex] = convertedValue;
        args.rawArguments[paramIndex] = originalValue;
      }
      else {
                // 方法执行需要参数,但是resolvedValues中没有提供这个参数,也就是说这个参数是要自动注入到Bean中的
        MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
        // 不是自动注入,直接抛出异常
        if (!autowiring) {
          // 抛出UnsatisfiedDependencyException,在调用该方法处会被捕获
        }
        try {
                    // 自动注入的情况下,调用getBean获取需要注入的Bean
          Object autowiredArgument =
              resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter);
                    // 把getBean返回的Bean封装到本次方法执行时需要的参数数组中去
          args.rawArguments[paramIndex] = autowiredArgument;
          args.arguments[paramIndex] = autowiredArgument;
                    // 标志这个参数是自动注入的
          args.preparedArguments[paramIndex] = new AutowiredArgumentMarker();
                    // 自动注入的情况下,在第二次调用时,需要重新处理,不能直接缓存
          args.resolveNecessary = true;
        }
        catch (BeansException ex) {
          // 抛出UnsatisfiedDependencyException,在调用该方法处会被捕获
        }
      }
    }
      // 注册Bean之间的依赖关系
    for (String autowiredBeanName : autowiredBeanNames) {
      this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
      if (logger.isDebugEnabled()) {
        logger.debug("Autowiring by type from bean name '" + beanName +
            "' via " + (executable instanceof Constructor ? "constructor" : "factory method") +
            " to bean named '" + autowiredBeanName + "'");
      }
    }
    return args;
  }

上面这段代码说难也难,说简单也简单,如果要彻底看懂它到底干了什么还是很有难度的。简单来说,它就是从第四段代码解析出来的参数中查找当前的这个候选方法需要的参数。如果找到了,那么尝试对其进行类型转换,将其转换成符合方法要求的类型,如果没有找到那么还需要判断当前方法的这个参数能不能进行自动注入,如果可以自动注入的话,那么调用getBean得到需要的Bean,并将其注入到方法需要的参数中。


第六段


public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    //  第一段代码:到这里已经得到了一个BeanWrapper,并对这个BeanWrapper做了初始化
    //  第二段代码:明确了实例化当前这个Bean到底是静态工厂还是实例工厂
    //  第三段代码:以及从缓存中取过了对应了方法以及参数
    //  第四段代码:明确了方法需要的最小的参数数量并对配置文件中的标签属性进行了一次解析
    //  第五段代码:到这里已经确定了可以使用来实例化Bean的方法是哪个
  // 省略抛出异常的代码,就是在对推断出来的方法做验证
    // 1.推断出来的方法不能为null
    // 2.推断出来的方法返回值不能为void
    // 3.推断出来的方法不能有多个
    // 对参数进行缓存
      if (explicitArgs == null && argsHolderToUse != null) {
         argsHolderToUse.storeCache(mbd, factoryMethodToUse);
      }
   }
   try {
      Object beanInstance;
      if (System.getSecurityManager() != null) {
         final Object fb = factoryBean;
         final Method factoryMethod = factoryMethodToUse;
         final Object[] args = argsToUse;
         beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
               beanFactory.getInstantiationStrategy().instantiate(mbd, beanName, beanFactory, fb, factoryMethod, args),
               beanFactory.getAccessControlContext());
      }
      else {
          // 反射调用对应方法进行实例化
          // 1.获取InstantiationStrategy,主要就是SimpleInstantiationStrategy跟CglibSubclassingInstantiationStrategy,其中CglibSubclassingInstantiationStrategy主要是用来处理beanDefinition中的lookupMethod跟replaceMethod。通常来说我们使用的就是SimpleInstantiationStrateg
          // 2.SimpleInstantiationStrateg就是单纯的通过反射调用方法
         beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(
               mbd, beanName, this.beanFactory, factoryBean, factoryMethodToUse, argsToUse);
      }
    // beanWrapper在这里对Bean进行了包装
      bw.setBeanInstance(beanInstance);
      return bw;
   }
   catch (Throwable ex) {
      throw new BeanCreationException(mbd.getResourceDescription(), beanName,
            "Bean instantiation via factory method failed", ex);
   }
}

上面这段代码的主要目的就是

1.缓存参数,原型可能多次创建同一个对象

2.反射调用推断出来的factoryMethod
/

相关文章
|
1天前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
29天前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
59 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
20天前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
29天前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
62 1
|
1月前
|
Java Spring
获取spring工厂中bean对象的两种方式
获取spring工厂中bean对象的两种方式
27 1
|
1月前
|
前端开发 Java Spring
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
100 2
|
1月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细介绍了Spring框架中的核心概念——Spring Bean的生命周期,包括实例化、属性赋值、接口回调、初始化、使用及销毁等10个阶段,并深入剖析了相关源码,如`BeanFactory`、`DefaultListableBeanFactory`和`BeanPostProcessor`等关键类与接口。通过理解这些核心组件,读者可以更好地掌握Spring Bean的管理和控制机制。
75 1
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
27天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
139 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决