Spring源码分析(八) Spring中推断构造方法源码解析

先补充一下@Configuration 注解的作用。

上节的源码分析中在bean工厂后置处理器 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry 中解析配置类并开始扫描得到所有的 BeanDefinition 注册到容器里 。

开始扫描,是从"配置类"作为入口,一步一步把所有涉及到的bean扫描出来的。在Spring中判断是不是"配置类的"时候,并不是直接根据是否加了@Configuration 注解来的。

 

 在 ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory) 会判断是不是"配置类" ,然后添加到 configCandidates 集合中后面一块处理。

这个判断方法里里主要判断逻辑:

 

 看到如果真正加了@Configuration的配置类,它的BeanDefinition中一个属性为  beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);  ,这个属性的作用就决定了加@Configuration和不加,对bean创建的影响。

@Configuration
@ComponentScan(value = "com.my.ioc.pojo.vo")
//@Import({ Myregister.class,MyImport.class})
public class BeanConfig {
    @Bean
    public People people1()
    {
        Bird bird =bird();
        return new People("people1");
    }
    @Bean
    public Bird bird(){
        Bird bird = new Bird();
        bird.setName("bad bird");
        return bird;
    }
}
@Component
public class AppConfig {
    @Bean
    public People people1()
    {
        Bird bird =bird();
        return new People("people1");
    }
    @Bean
    public Bird bird(){
        Bird bird = new Bird();
        bird.setName("bad bird");
        return bird;
    }
}

上面两种方式people1,和bird两个bean都能实例化创建。但是是有区别的。加了@Configuration的配置类,在类中进行方法相互调用的时候,会走代理的逻辑。因为加了@Configuration 的bd 中beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);  属性为full,  在ConfigurationClassPostProcessor#postProcessBeanFactory 中进行处理的时候,会根据属性为full,  覆盖beanDefinition中的beanClass属性,重新设置成一个代理类,这样其实在spring中使用BeanConfig,其实是代理类在处理。而不加@Configuration,就不是代理类来处理。

导致的结果就是。AppConfig中的在people1()方法中调用bird() ,得到会是一个新的对象,而不是单例的,因为它不会从spring容器中拿。而在BeanConfig中呢,在people1()方法中调用bird() ,得到的一直都是同一个对象,因为在Bird实例创建完之后会放到spring容器,其他地方调用bird()的时候,不会再新创建而是从容器中获取。在代理类中这部分逻辑是通过判断当前调用的方法名people1和被调用的方法名bird是不是同一个,如果不是同一个,则它尝试用方法bird从spring容器中获取一个实例返回出来。

增强类以及逻辑在 ConfigurationClassEnhancer#BeanMethodInterceptor# intercept 中。

private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

        /**
         * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
         * existence of this bean object.
         * @throws Throwable as a catch-all for any exception that may be thrown when invoking the
         * super implementation of the proxied method i.e., the actual {@code @Bean} method
         */
        @Override
        @Nullable
        public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
                    MethodProxy cglibMethodProxy) throws Throwable {
            // 得到bean工厂和当前正在调用的beanMethod对应的beanName
            ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
            String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

            // Determine whether this bean is a scoped-proxy
            if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
                String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
                if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
                    beanName = scopedBeanName;
                }
            }

            // To handle the case of an inter-bean method reference, we must explicitly check the
            // container for already cached instances.

            // First, check to see if the requested bean is a FactoryBean. If so, create a subclass
            // proxy that intercepts calls to getObject() and returns any cached bean instance.
            // This ensures that the semantics of calling a FactoryBean from within @Bean methods
            // is the same as that of referring to a FactoryBean within XML. See SPR-6602.
            // 如果beanFactroy中存在beanName 并且也存在&+beanName所对应的bean  那么这个bean就是一个FactoryBean
            if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
                    factoryContainsBean(beanFactory, beanName)) {
                Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
                if (factoryBean instanceof ScopedProxyFactoryBean) {
                    // Scoped proxy factory beans are a special case and should not be further proxied
                }
                else {
                    // 如果是FactoryBean 就对他进行代理  从缓存中得到getObject()的值
                    // It is a candidate FactoryBean - go ahead with enhancement
                    return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
                }
            }
            // 比如当前正在实例化的方法是userService()方法 所对应的bean,而userService()方法中调用user()方法
            // 这个时候下面的这个判断等于false 就不会真正的去执行user()方法 会直接去beanFactory中去获取bean
            if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
                // The factory is calling the bean method in order to instantiate and register the bean
                // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
                // create the bean instance.
                if (logger.isInfoEnabled() &&
                        BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
                    logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
                                    "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
                                    "result in a failure to process annotations such as @Autowired, " +
                                    "@Resource and @PostConstruct within the method's declaring " +
                                    "@Configuration class. Add the 'static' modifier to this method to avoid " +
                                    "these container lifecycle issues; see @Bean javadoc for complete details.",
                            beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
                }
                // 走的是本身的逻辑
                return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
            }
            // 这里是真正代理逻辑
            return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
        }

 

分析

Spring中的一个bean,需要实例化得到一个对象,而实例化就需要用到构造方法。

 

一般情况下,一个类只有一个构造方法:

  1. 要么是无参的构造方法
  2. 要么是有参的构造方法

 

如果只有一个无参的构造方法,那么实例化就只能使用这个构造方法了。

如果只有一个有参的构造方法,那么实例化时能使用这个构造方法吗?要分情况讨论:

  1. 使用AnnotationConfigApplicationContext,会使用这个构造方法进行实例化,那么Spring会根据构造方法的参数信息去寻找bean,然后传给构造方法
  2. 使用ClassPathXmlApplicationContext,表示使用XML的方式来使用bean,要么在XML中指定构造方法的参数值(手动指定),要么配置autowire=constructor让Spring自动去寻找bean做为构造方法参数值。

 

上面是只有一个构造方法的情况,那么如果有多个构造方法呢?

 

又分为两种情况,多个构造方法中存不存在无参的构造方法。

 

分析:一个类存在多个构造方法,那么Spring进行实例化之前,该如何去确定到底用哪个构造方法呢?

  1. 如果开发者指定了想要使用的构造方法,那么就用这个构造方法
  2. 如果开发者没有指定想要使用的构造方法,则看开发者有没有让Spring自动去选择构造方法
  3. 如果开发者也没有让Spring自动去选择构造方法,则Spring利用无参构造方法,如果没有无参构造方法,则报错

 

 

针对第一点,开发者可以通过什么方式来指定使用哪个构造方法呢?

  1. xml中的<constructor-arg>标签,这个标签表示构造方法参数,所以可以根据这个确定想要使用的构造方法的参数个数,从而确定想要使用的构造方法
  2. 通过@Autowired注解,@Autowired注解可以写在构造方法上,所以哪个构造方法上写了@Autowired注解,表示开发者想使用哪个构造方法,当然,它和第一个方式的不同点是,通过xml的方式,我们直接指定了构造方法的参数值,而通过@Autowired注解的方式,需要Spring通过byType+byName的方式去找到符合条件的bean作为构造方法的参数值

 

再来看第二点,如果开发者没有指定想要使用的构造方法,则看开发者有没有让Spring自动去选择构造方法,对于这一点,只能用在ClassPathXmlApplicationContext,因为通过AnnotationConfigApplicationContext没有办法去指定某个bean可以自动去选择构造方法,而通过ClassPathXmlApplicationContext可以在xml中指定某个bean的autowire为constructor,虽然这个属性表示通过构造方法自动注入,所以需要自动的去选择一个构造方法进行自动注入,因为是构造方法,所以顺便是进行实例化。

 

当然,还有一种情况,就是多个构造方法上写了@Autowired注解,那么此时Spring会报错。

但是,因为@Autowired还有一个属性required,默认为ture,所以一个类中,只有能一个构造方法标注了@Autowired或@Autowired(required=true),有多个会报错。但是可以有多个@Autowired(required=false),这种情况下,需要Spring从这些构造方法中去自动选择一个构造方法。

 

创建过程源码思路

 

  1. AbstractAutowireCapableBeanFactory类中的createBeanInstance()方法会去创建一个Bean实例
  2. 根据BeanDefinition加载类得到Class对象
  3. 如果BeanDefinition绑定了一个Supplier,那就调用Supplier的get方法得到一个对象并直接返回
  4. 如果BeanDefinition中存在factoryMethodName,那么就调用该工厂方法得到一个bean对象并返回
  5. 如果BeanDefinition已经自动构造过了,那就调用autowireConstructor()自动构造一个对象
  6. 调用SmartInstantiationAwareBeanPostProcessor的determineCandidateConstructors()方法得到哪些构造方法是可以用的
  7. 如果存在可用得构造方法,或者当前BeanDefinition的autowired是AUTOWIRE_CONSTRUCTOR,或者BeanDefinition中指定了构造方法参数值,或者创建Bean的时候指定了构造方法参数值,那么就调用autowireConstructor()方法自动构造一个对象
  8. 最后,如果不是上述情况,就根据无参的构造方法实例化一个对象

 

autowireConstructor()

  1. 先检查是否指定了具体的构造方法和构造方法参数值,或者在BeanDefinition中缓存了具体的构造方法或构造方法参数值,如果存在那么则直接使用该构造方法进行实例化
  2. 如果没有确定的构造方法或构造方法参数值,那么
    1. 如果没有确定的构造方法,那么则找出类中所有的构造方法
    2. 如果只有一个无参的构造方法,那么直接使用无参的构造方法进行实例化
    3. 如果有多个可用的构造方法或者当前Bean需要自动通过构造方法注入
    4. 根据所指定的构造方法参数值,确定所需要的最少的构造方法参数值的个数
    5. 对所有的构造方法进行排序,参数个数多的在前面
    6. 遍历每个构造方法
    7. 如果不是调用getBean方法时所指定的构造方法参数值,那么则根据构造方法参数类型找值
    8. 如果时调用getBean方法时所指定的构造方法参数值,就直接利用这些值
    9. 如果根据当前构造方法找到了对应的构造方法参数值,那么这个构造方法就是可用的,但是不一定这个构造方法就是最佳的,所以这里会涉及到是否有多个构造方法匹配了同样的值,这个时候就会用值和构造方法类型进行匹配程度的打分,找到一个最匹配的

determineCandidateConstructors()方法执行流程:

 

 

为什么分越少优先级越高?

 

主要是计算找到的bean和构造方法参数类型匹配程度有多高。

 

假设bean的类型为A,A的父类是B,B的父类是C,同时A实现了接口D

如果构造方法的参数类型为A,那么完全匹配,得分为0

如果构造方法的参数类型为B,那么得分为2

如果构造方法的参数类型为C,那么得分为4

如果构造方法的参数类型为D,那么得分为1

 

可以直接使用如下代码进行测试:

Object[] objects = new Object[]{new A()};

// 0
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{A.class}, objects));

// 2
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{B.class}, objects));

// 4
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{C.class}, objects));

// 1
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{D.class}, objects));

 

 

所以,我们可以发现,越匹配分数越低。

构造函数选择的流程图解:

 

 

posted @ 2021-07-04 16:47  蒙恬括  阅读(263)  评论(0编辑  收藏  举报