美文网首页Spring
AutowireCapableBeanFactory探密(2)—

AutowireCapableBeanFactory探密(2)—

作者: ZX_周雄 | 来源:发表于2020-01-14 14:04 被阅读0次

    回顾

    上篇,介绍了AutowireCapableBeanFactory的作用:让Spring管理的Bean去装配和填充那些不被Spring托管的Bean,为第三方框架赋能。其中,介绍AutowireCapableBeanFactory接口时简单提了一下该接口定义了6个属性,其中有5个(AUTOWIRE_*)是与autowireMode相关的常量值定义,用于描述该用何种方式来进行装配Bean

    正文

    这里有个容易让人误解的点,就是关于autowireMode的各个常量的命名。其中有两个常量定义分别是int AUTOWIRE_BY_NAME = 1int AUTOWIRE_BY_TYPE = 2,从命名上看跟我们最常用的@Resouce、@Autowired这种注解使用的方式很像,但实际上二者八杆子打不着关系,千万不要把他们混为一谈

    实际上,注解注入方式在Spring中被称为annotation-driven injection,而autowireMode这种装配在Spring里其实被称为traditional autowiring

    autowireMode之所以称为traditional,是因为这种使用姿势已经足够老,老到新一代的Javaer,甚至都没有在项目中用过这种装配模式,上来就是注解也能很好地work,因此,traditional autowiring在Spring 2.5后已式微,大概只有在古董项目上才能窥之身影

    先来看一下,Spring 2.5以前项目里如何使用traditional autowiring

    定义案例需要用到的类FooServiceBarService,其中FooService依赖BarService

    public class FooService {
        private BarService barService;
    
        public void setBarService(BarService barService) {
            this.barService = barService;
        }
    }
    
    public class BarService {
    }
    

    在很久很久以前,那时候Spring还没有大量使用注解,也没有提供各类注解给应用层使用,各类配置项在都是依靠XML进行配置,如下所示

    <bean id="fooService" class="com.example.demo.service.FooService">
        <property name="barService" ref="barService"/>
    </bean>
    
    <bean id="barService" class="com.example.demo.service.BarService"/>
    

    需要用户在XML配置文件中定义Spring需要管理哪些Bean,并通过手动装配方式指定依赖关系。如果只有两个Bean以及它们之间的依赖关系,这种方式也很OK,但如果有大量的Bean呢,都要手动指定那得多麻烦枯燥

    byName

    懒惰是科技进步的动力,程序员天性懒惰但又很聪明,想出了自动装配的方式,如下所示:

    <bean id="fooService" class="com.example.demo.service.FooService" autowire="byName"/>
    
    <bean id="barService" class="com.example.demo.service.BarService"/>
    

    这样,无论FooService里依赖多少其它Bean,都不再需要手动一个个装配,通过autowire="byName"的方式,Spring 就会在构建FooService之后,内过Java Bean内省机制拿到Bean里所有的propertyName(案例中的propertyName:barService),然后上Spring IoC容器中找到beanName为propertyName的bean(barService)并将之注入,完成装配。这种方式要求propertyName与beanName一致,故命名为byName

    byType

    如果想要想打破上面的限制,不要求propertyName与beanName一致也能完成注入,可以配置为autowire="byType",如下所示:

    <bean id="fooService" class="com.example.demo.service.FooService" autowire="byType"/>
    
    <bean id="baz" class="com.example.demo.service.BarService"/>
    

    甚至将FooService对BarService的依赖改的"面目全非"(此处,propertyName:quz)

    public class FooService {
        private BarService xxx;
    
        public void setQuz(BarService xxx) {
            this.xxx = xxx;
        }
    }
    

    将BarService的bean id设置为baz,尽管与FooService中的propertyName(quz)不一致,但通过指定autowire="byType",依然能够将barService注入到fooService实例中。

    原理是: Spring 在构建FooService之后,内过Java Bean内省机制拿到Bean里所有的propertyName(案例中的propertyName:quz),最终拿到DependencyDescriptor(依赖描述符,它描述了一个待注入的依赖信息:要么是构造器参数,要么是方法参数,要么是字段,并且提供了非常友好的、一种统一的方式去访问)。在本案例中,DependencyDescriptor描述的是:我的名字叫setQuz,是在FooService类中被声名的(declaringClass),需要一个入参且其类型是BarService(parameterTypes、resolvableType)。Spring在IoC容器中找到type为BarService的bean返回并注入,完成装配。这种方式只要求待注入的参数,其类型能在Spring里找到,故命名为byType

    小总结:无论是byName还是byType,Spring都是通过Java Bean的内省机制找到property,然后上IoC容器中找到对应的Bean来完成的注入,这也就是古董项目重充斥大量setter方法的缘由

    简单看一下源码中,处理byNamebyType的地方,依然是在populateBean方法内部

    // org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
    
    //如果是byName或byType
    if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
        MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
        // Add property values based on autowire by name if applicable.
        if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME) {
            autowireByName(beanName, mbd, bw, newPvs);
        }
        // Add property values based on autowire by type if applicable.
        if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
            autowireByType(beanName, mbd, bw, newPvs);
        }
        pvs = newPvs;
    }
    // ...(省略)[省略的内容就包括处理@Resouce、@Autowired注解驱动注入的部分]
    
    // 上面是收集属性到pvs,并不执行属性装配的动作,下边才真正执行
    if (pvs != null) {
        applyPropertyValues(beanName, mbd, bw, pvs);
    }
    

    通过判断autowireMode是byName还是byType,分别进入autowireByNameautowireByType方法,将待装配的对象收集到pvs,最后再通过applyPropertyValues真正执行属性装配。此处再次提醒:该段逻辑与处理@Resouce、@Autowired的注解驱动注入无关,省略部分是上篇内容,即上篇内容提到ibp.postProcessProperties才是处理annotation-driven injection的逻辑的地方,且真正执行了注入的逻辑

    pvs是MutablePropertyValues类型实例,该类主要职责是维护内部的PropertyValue集合,实现了PropertyValues接口,提供对集合的增删查操作

    public class MutablePropertyValues implements PropertyValues, Serializable {
    
        private final List<PropertyValue> propertyValueList;
        // ...(省略)
    }
    
    public interface PropertyValues extends Iterable<PropertyValue>
    

    PropertyValue代表的是某个Bean属性的属性名与属性值,类似于一个键值对

    public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {
    
        private final String name;
    
        @Nullable
        private final Object value;
        // ...(省略)
    }
    

    因此,pvs也即List<PropertyValue>代表了某个Bean里所有的属性名及属性值

    constructor

    byNamebyType都是setter方法注入,接着看看autowireMode = constructor构造器装配使用姿势

    public class FooService {
        private BarService barService;
    
        public FooService(BarService barService) {
            this.barService = barService;
        }
    }
    
    <bean id="fooService" class="com.example.demo.service.FooService" autowire="constructor"/>
    
    <bean id="baz" class="com.example.demo.service.BarService"/>
    

    通过在FooService中定入参为BarService的构造函数,在定义Spring bean时指定autowire="constructor",那么Spring在构造FooService实例时,就会找到该构造函数,并从IoC容器中找到类型为BarService的bean,传入构造函数中进行构造,如此,生成的FooService实例就自动注入了BarService

    原理涉及的核心代码如下:

    // org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
    
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    // 如果找到合适的构造器或者autowireMode = AUTOWIRE_CONSTRUCTOR 则执行autowireConstructor
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
            mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        return autowireConstructor(beanName, mbd, ctors, args);
    }
    

    这里有一点特殊的地方值得一提:本案例中不配置autowire="constructor",也能够通过构造器进行装配,原因在于determineConstructorsFromBeanPostProcessors的返回值如果不为空,表明找到了合适的构造器,也会进入autowireConstructor中执行构造器装配

    // org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
    
    // ...(省略)
    if (!candidates.isEmpty()) { 
        // 与@Autowired、@Value 注入有关,不是本节重点,暂时忽略
        // Add default constructor to list of optional constructors, as fallback.
        if (requiredConstructor == null) {
            if (defaultConstructor != null) {
                candidates.add(defaultConstructor);
            }
            else if (candidates.size() == 1 && logger.isInfoEnabled()) {
                // ...(省略,log信息)
            }
        }
        candidateConstructors = candidates.toArray(new Constructor<?>[0]);
    }
    else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
        candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; // 有且只有一个非空参构造函数
    }
    // ...(省略两个else if,它们与kotlin相关,跳过不看)
    else {
        candidateConstructors = new Constructor<?>[0];
    }
    
    return (candidateConstructors.length > 0 ? candidateConstructors : null);
    

    在目前的Spring实现中,AutowiredAnnotationBeanPostProcessor实现了determineCandidateConstructors方法,如果找到有且只有一个非空参构造器,那么就可以成功返回
    ,很显然,我们的FooService符合这样的条件,因此返回public FooService(BarService barService)构造器,进入autowireConstructor中执行构造器装配

    假如给FooService再添加一个空参构造器,或者别的带参构造器,就不再满足上述条件,需要配置autowire="constructor"才能实现构造器注入

    AUTODETECT

    该装配模式是byTypeconstructor的混合体,从Spring 3.0已经标识为Deprecated,Spring认为,如果你不想关注到底是哪种autowireMode,就想闭着眼睛让Spring帮忙注入,那就用注解注入的方式吧,这样同样实现效果的同时,也能让注入语义更清晰一些

    // org.springframework.beans.factory.support.AbstractBeanDefinition#getResolvedAutowireMode
    
    public int getResolvedAutowireMode() {
        if (this.autowireMode == AUTOWIRE_AUTODETECT) {
            // Work out whether to apply setter autowiring or constructor autowiring.
            // If it has a no-arg constructor it's deemed to be setter autowiring,
            // otherwise we'll try constructor autowiring.
            Constructor<?>[] constructors = getBeanClass().getConstructors();
            for (Constructor<?> constructor : constructors) {
                if (constructor.getParameterCount() == 0) {
                    return AUTOWIRE_BY_TYPE;
                }
            }
            return AUTOWIRE_CONSTRUCTOR;
        }
        else {
            return this.autowireMode;
        }
    }
    

    判断的逻辑很简单,如果存在空参构造函数,就认为是想用byType的装配模式,否则就是constructor的构造模式

    由于Spring 已经将这种autowireMode标识为Deprecated,且除了稍显简便也没别的其他优势,我们还是能不用则不用

    AUTOWIRE_NO

    默认的autowireMode就是不装配,无特殊行为,不作过多介绍

    到了Spring 3.0,XML配置方式已经开始展现颓势,官方推出了Annotation Config,搭配@Configuration、@Bean、@DependsOn等注解,作为XML的等价配置方式,试图取代XML的配置地位,从今日的结果看来,这一目的已经达成,新项目中基本不再出现旧的配置方式

    简单介绍一下Annotation Config如何配置autowireMode,以下两种形式是等价的:

    <bean id="fooService" class="com.example.demo.service.FooService" autowire="byName"/>
    
    <bean id="barService" class="com.example.demo.service.BarService"/>
    
    @Bean(autowire = Autowire.BY_NAME)
    public FooService fooService() {
        return new FooService();
    }
    
    @Bean
    public BarService BarService() {
        return new BarService();
    }
    

    @Bean注解有一个autowire属性,取值类似于autowireMode,不过在此处它的取值只有三个:NO、BY_NAME、BY_TYPE,没有CONSTRUCT、AUTODETECT,简单想一下可知道,Annotation Config的配置方式不适合CONSTRUCT,以及可能包含CONSTRUCT方式的AUTODETECT。
    无论XML配置还是Annotaion Config配置autowireMode,它们的底层原理都一样,最终都是给AbstractBeanDefinitionautowireMode属性赋值,因此在真正执行装配时都是判断BeanDefinition的autowireMode属性值来决定装配模式,默认取值: AUTOWIRE_NO

    此外,上篇文章提到的AutowireCapableBeanFactory有很多细粒度控制Bean生命周期的方法,如autowire、autowireBeanProperties等带有autowireMode参数,作用与上面介绍的是一样的,最终都落实到AbstractBeanDefinitionautowireMode属性上,也会将待装配对象收集到pvs,最后调用applyPropertyValues方法执行装配


    以上便是传统装配模式的介绍,接下来将介绍现代注解驱动注入方式

    Annotation-driven injection的方式,在目前的应用开发中被广泛的运用,打开任意一个三层模型(Controller-Service-Dao)的Java Web项目,在各层总是能看到@Resouce、@Autowired、@Value、@Inject(javax.inject.Inject),甚至是自定义注入注解(AutowiredAnnotationBeanPostProcessor#setAutowiredAnnotationType提供这个能力,不需要太多工作量,很迅速地完成自定义),Spring赋予了这些注解DI的能力 ,总体上可作用于Field,Setter Method,Construct,Parameter,AnnotationType(不同注解作用的范围并不一致),再加上注解自身能够配置的属性、与其它注解的配合(如@Qualifier)使用,能玩出的花样真是不胜枚举。他们之间的功能有重叠有交叉,不必要每一个都掌握,徒增精力,只掌握最小子集的注解又可涵盖注解功能的总和,满足任何场景的开发即可。故此,本节探讨一下大概率使用最多的@Resouce、@Autowired在字段注入的场景

    @Resouce

    案例代码如下:

    @Service
    public class FooService {
        @Resource
        private BarService barService;
    }
    
    @Service
    public class BarService {}
    

    Spring 处理@Resouce体现在Spring Bean Lifecycle的两个阶段

    1. 构建Bean实例之后,填充属性之前,收集Bean里面所有@Resouce相关的信息并缓存起来,注意,此阶段是作信息收集之用,不作具体处理(CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition)
    2. 在填充属性的过程中,将待装配的对象收集到pvs后,执行applyPropertyValues之前(执行传统的属性装配之前),将第1阶段收集到的信息利用起来,执行Annotation-driven injection(CommonAnnotationBeanPostProcessor#postProcessProperties)

    先看第一阶段,收集待注入的元信息

    // org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
    
    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
        // 收集待注入的元信息(@Resouce相关信息)
        InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
        metadata.checkConfigMembers(beanDefinition);
    }
    
    private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {
        // ...(省略)
        // 构建待注入元信息,并放入缓存
        metadata = buildResourceMetadata(clazz);
        this.injectionMetadataCache.put(cacheKey, metadata);
    
        return metadata;
    }
    
    // 构建待注入元信息
    private InjectionMetadata buildResourceMetadata(final Class<?> clazz) {
        List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
        Class<?> targetClass = clazz;
    
        do {
            final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
    
            // 通过反射查看class所有field上是否含有@Resouce注解,找到后就封装成ResourceElement
            ReflectionUtils.doWithLocalFields(targetClass, field -> {
                // ...(省略)
                else if (field.isAnnotationPresent(Resource.class)) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        throw new IllegalStateException("@Resource annotation is not supported on static fields");
                    }
                    if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
                        currElements.add(new ResourceElement(field, field, null));
                    }
                }
            });
            // ...(下边是收集方法注入方式的元信息,省略)
    
            elements.addAll(0, currElements);
            targetClass = targetClass.getSuperclass();
        }
        while (targetClass != null && targetClass != Object.class);
    
        return new InjectionMetadata(clazz, elements);
    }
    

    通过源码可以看到,最后待注入的元信息其实是ResourceElement,看着看一下ResourceElement的构造函数做了些什么事情

    // 从上面的buildResourceMetadata可以看出,member、ae都是field
    public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
        super(member, pd);
        Resource resource = ae.getAnnotation(Resource.class);
        String resourceName = resource.name();
        Class<?> resourceType = resource.type();
        this.isDefaultName = !StringUtils.hasLength(resourceName);
        
        // 处理resourceName
        if (this.isDefaultName) {
            // 如果使用@Resource时未指定name,默认取member的name做为resouceName
            // 对于member为field的case,取的field name
            resourceName = this.member.getName();
            
            //如果member是个方法,代表setter注入,那就把setXxx前的set去掉后将首字段小写作为resouceName,即xxx
            if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
                resourceName = Introspector.decapitalize(resourceName.substring(3));
            }
        }
        else if (embeddedValueResolver != null) {
            // 如果手动指定resoureName了,就使用embeddedValueResolver.resolveStringValue去解析
            resourceName = embeddedValueResolver.resolveStringValue(resourceName);
        }
        
        // 处理resouceType
        if (Object.class != resourceType) {
            // 如果手动指了定resouceType,需要检查一下是不是瞎指定,比如field明明是个A,却通过resouceType指定为B type,那明显会有问题
            checkResourceType(resourceType);
        }
        else {
            // No resource type specified... check field/method.
            // 如果未手动指定resourceType,则取member或者pd的类型
            resourceType = getResourceType();
        }
        this.name = (resourceName != null ? resourceName : "");
        this.lookupType = resourceType;
        String lookupValue = resource.lookup();
        this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
        Lazy lazy = ae.getAnnotation(Lazy.class);
        this.lazyLookup = (lazy != null && lazy.value());
    }
    

    构造函数主要就是给name、lookupType、lookupValue等属性赋值,其中name为resourceName,lookupType为resourceType,而且一般都不会为空,即便我们没有通过注解手动指定它们的值,也有默认的方式获取resourceName跟resourceType,这也就是我们在多数场景直接打上注解就完事,而不指定具体参数的原因

    这种设计原则值得借鉴与学习:如果我们需要给他人提供某项能力,出于优雅设计的原则,首先调研、考虑大多数场景怎样使用,我们将这类应用的场景以代码的形式固化下来,当发生API调用时不需要提供过多参数也可以正常运行,针对另外一小部分需要自定义的场景,通过提供一种optional的能力,允许用户选填,选填项优先级高于固化代码的方式,就能覆盖100%的场景。这也是一种"约定优于配置(convention over configuration)"的思想实践

    另外,如果我们通过注解的属性手动指定了name,会走resourceName = embeddedValueResolver.resolveStringValue(resourceName);这段逻辑,embeddedValueResolver是EmbeddedValueResolver类的实例,该类特别强大,不但能够解析"占位符",还能够解析"Spring EL"表达式,简言之,@Value里能配置啥值,在这就能配啥值,而且解析的效果是一样的。因此,如下的方式也是与上边的方式等下的

    // application.properties
    quz = barService
    
    // ----------
    public class FooService {
        @Resource(name = "${quz}")
        private BarService barService;
    }
    

    Spring 的设计真是惊为天人,考虑到各种我们开发过程中可能使用到的姿势并且提供了能力,真是让人佩服五体投地

    再看第二阶段,执行真正的注入逻辑

    // org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties
    
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
        // 该方法上面出现过,第一次出现的时候是构建并且放入缓存,这是第二次出现 ,直接从缓存中获取
        // ResourceElement是InjectionMetadata的子类,因此metadata其实是ResourceElement的实例
        InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
        try {
            // 执行注入
            metadata.inject(bean, beanName, pvs);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
        }
        return pvs;
    }
    
    // org.springframework.beans.factory.annotation.InjectionMetadata#inject
    
    public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        Collection<InjectedElement> checkedElements = this.checkedElements;
        Collection<InjectedElement> elementsToIterate =
                (checkedElements != null ? checkedElements : this.injectedElements);
        if (!elementsToIterate.isEmpty()) {
            // 循环每一个待注入的元素,执行注入
            for (InjectedElement element : elementsToIterate) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Processing injected element of bean '" + beanName + "': " + element);
                }
                // 注入
                element.inject(target, beanName, pvs);
            }
        }
    }
    

    通过反射执行注入逻辑field.set(target, getResourceToInject(target, requestingBeanName));

    
    protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
            throws Throwable {
    
        if (this.isField) {
            Field field = (Field) this.member;
            ReflectionUtils.makeAccessible(field);
            field.set(target, getResourceToInject(target, requestingBeanName));
        }
        // ...(方法注入,省略)
    }
    

    接着看getResourceToInject(target, requestingBeanName),获取待注入对象实例

    // org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject
    
    protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
        // 我们并没有配置@Lazy注解,因此走`getResource(this, requestingBeanName)`逻辑
        return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
                getResource(this, requestingBeanName));
    }
    
    protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
            throws NoSuchBeanDefinitionException {
        // ...(省略)
        // resourceFactory是BeanFactory的实例,在构建CommonAnnotationBeanPostProcessor实例的时候通过BeanFactoryAware#setBeanFactory回调接口注入进来
        return autowireResource(this.resourceFactory, element, requestingBeanName);
    }
    
    // org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource
    
    protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
                throws NoSuchBeanDefinitionException {
    
        Object resource;
        Set<String> autowiredBeanNames;
        String name = element.name;
    
        // factory是DefaultListableBeanFactory的实例对象,因此也就是AutowireCapableBeanFactory的实例对象
        if (factory instanceof AutowireCapableBeanFactory) { // (if 1)
            AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
            DependencyDescriptor descriptor = element.getDependencyDescriptor();
            // fallbackToDefaultTypeMatch默认值为true
            if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) { // (if 2)
                autowiredBeanNames = new LinkedHashSet<>();
                // resouce为解析出来的待注入实例对象
                resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
                if (resource == null) {
                    throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
                }
            }
            else { // (else 1)
                // resouce为解析出来的待注入实例对象
                resource = beanFactory.resolveBeanByName(name, descriptor);
                autowiredBeanNames = Collections.singleton(name);
            }
        }
        else { // (else 2)
            // 基本上不会考到这
            resource = factory.getBean(name, element.lookupType);
            autowiredBeanNames = Collections.singleton(name);
        }
    
        // factory是DefaultListableBeanFactory的实例对象,因此也就是ConfigurableBeanFactory的实例对象
        if (factory instanceof ConfigurableBeanFactory) { // (if 3)
            ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
            for (String autowiredBeanName : autowiredBeanNames) {
                if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
                    // 将依赖关系注册到Spring中,是个双向的描述
                    // 即dependentBeanMap、dependenciesForBeanMap
                    beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
                }
            }
        }
    
        return resource;
    }
    

    factory是DefaultListableBeanFactory的实例对象,因此也就是AutowireCapableBeanFactory、ConfigurableBeanFactory的实例对象,所以if 1、if 3都会进去,if 1逻辑用于解析待注入的bean,if 3逻辑是维护依赖Bean与被依赖Bean之间的关系

    if 2代码块中,有个名为fallbackToDefaultTypeMatch的属性,它表示的含义是:当未通过注解手动指定name(意味着使用defaultName,即字段名或属性名),Spring根据defaultName上Ioc容器找不到Bean时,是否需要fallback,根据type到Ioc容器中找,默认值为true

    因此进入if 2块需同时满足以下三个条件:

    1. fallbackToDefaultTypeMatch = true(默认值是true)
    2. element.isDefaultName = true(不能通过注解指定name)
    3. factory.containsBean(name) = false(根据name在Ioc容器中找不到Bean)

    否则,直接进入else 1的逻辑: resource = beanFactory.resolveBeanByName(name, descriptor);

    fallbackToDefaultTypeMatch默认true,一般也不会改为false,忽略这种情况不考虑,那进入else 1的情况有2个:

    1. 通过注解手动指定name,如@Resource(name = "quz")
    2. IoC中包含beanName = name的Bean或者BeanDefinition

    在本案例中,由于factory.containsBean("barService") = true,因此进入的是else 1的逻辑

    网上有说法:

    @Resouce默认根据name进行注入,如果找不到就根据type进行注入

    其实这种说法不够严谨,也不太准确,能成立的前提条件是:name是fieldName或者propertyName,即未通过注解进行明确指定。如若通过注解手动指定name,就会走else 1逻辑,压根不会根据type进行注入

    由于name、type都可以手动指定或不指定,因此出现4种排列组合的情况:

    1. 不指定name与type: 通过fileName或propertyName判断Ioc中是否存在,存在进入else 1 ,不存在就通过type去找,找不到或找到多个都抛异常(if 2)
    2. 仅指定name: 通过name找唯一的Bean,找不到抛异常(else 1)
    3. 仅指定type: 通过type找唯一的Bean,找不到或找到多个都抛异常(if 2)
    4. 指定name、指定type: 通过name找唯一的Bean,找不到抛异常(else 1)

    注: 不建议把这4个排列组合的结论记下来,而是记住原理,遇到问题能够根据原理分析出来原因并加以解决

    if 2、else 1都是将待注入的Bean解析出来,所谓解析就是在Spring容器中完成Bean的构造、属性填充、初始化等,然后返回一个能用的Bean。由于解析Bean是一个比较复杂的过程,涉及的知识点很多,不在本篇讨论的重点,暂且按下不表。只要知道它能返回一个Bean即可,接着,拿着这个返回结果(待注入对象)就可以完成注入

    @Autowired

    Spring 处理@Autowired同样体现在Spring Bean Lifecycle的两个阶段,与@Resouce处理方式如出一辙,甚至它们之间的代码有大部分都是相似的,因此熟悉@Resouce的处理流程之后,再来看@Autowired的处理代码,简直不能再轻松

    第一阶段,收集待注入的元信息(InjectionMetadata)

    AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition -> findAutowiringMetadata -> buildAutowiringMetadata
    

    第二阶段,执行真正的注入逻辑

    AutowiredAnnotationBeanPostProcessor#postProcessProperties -> findAutowiringMetadata -> InjectionMetadata#inject -> AutowiredFieldElement#inject
    
    // org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
    
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        Field field = (Field) this.member;
        Object value;
        if (this.cached) {
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
        }
        else {
            DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
            desc.setContainingClass(bean.getClass());
            Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
            Assert.state(beanFactory != null, "No BeanFactory available");
            TypeConverter typeConverter = beanFactory.getTypeConverter();
            try {
                // value为解析出来的待注入实例对象
                value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
            }
            catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
            }
            // ...(省略)
        }
        if (value != null) {
            ReflectionUtils.makeAccessible(field);
            field.set(bean, value);
        }
    }
    

    还是同样的配方,还是熟悉的味道,不过过程更简单了,因为没有name、type的属性,而是将field包装成DependencyDescriptor,通过beanFactory.resolveDependency根据类型解析出来待注入的实例对象,然后通过反射直接注入

    思考

    了解到传统装配模式现代注解驱动注入方式的区别之后,我们在具体开发过程中,该如何抉择呢?很显然,Spring的方式是在引导我们向Annotation-driven Injection靠拢,也就是在正常的业务代码中应该使用现代注解驱动注入的方式,这种方式已经是大家约定俗成的习惯用法,是行业共识,没有太多的勾通成本。实际上,在Annotation Config中使用autowireMode也是不被建议的,从Spring 5.1开始,@Bean注解的autowire属性已经被标识为Deprecated,文档上提到这种方式已经被@Autowired注解所取代

    那么是否意味着traditional autowiring就应该被打入冷宫,不应该使用了呢?其实不是的,至少从Spring 5.1看来,AbstractBeanDefinition的autowireMode还没有被标识为过期,AutowireCapableBeanFactory的细粒度生命周期方法带有autowireMode参数也没有被标识为过期,它们还在一些场合——为第三方框架赋能的场景发挥着光和热

    试想一下,如果我是一个第三方框架的开发者,开发的框架想与Spring结合,想借助它的一些能力,但是又不想与Spring结合得太过紧密,而是仅仅做为DI的可选项之一,那么设计上就要考虑松耦合,不能嵌入太多Spring相关的注解或者代码,而是独立一个-spring的模块,在该模块中引入AutowireCapableBeanFactory,根据场景配置不同的autowireMode,使之与框架自身需要DI的场景、对象进行结合,而框架的实例对象看起来与Spring毫不相关,只有这样,才能与Spring进行解耦合

    举个Dubbo的例子,在Dubbo DI过程,调用的是Object object = objectFactory.getExtension(pt, property);。objectFactory是一个AdaptiveExtensionFactory(自适应的DI工厂,是个composite模式),里边维护了一个ExtensionFactory(真正的工厂类)集合,该集合包含两个工厂,一个依靠Dubbo自身的SPI机制加载元素(SpiExtensionFactory),另一个直接从Spring获取元素(SpringExtensionFactory)

    public class AdaptiveExtensionFactory implements ExtensionFactory {
        private final List<ExtensionFactory> factories;
        // ...(省略)
    
        @Override
        public <T> T getExtension(Class<T> type, String name) {
    
            for (ExtensionFactory factory : factories) {
                T extension = factory.getExtension(type, name);
                // 任意一个工厂获取到元素,就立即返回
                if (extension != null) {
                    return extension;
                }
            }
            return null;
        }
    
    }
    
    public class SpiExtensionFactory implements ExtensionFactory {
        @Override
        public <T> T getExtension(Class<T> type, String name) {
            if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
                ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
                if (!loader.getSupportedExtensions().isEmpty()) {
                    return loader.getAdaptiveExtension();
                }
            }
            return null;
        }
    }
    
    public class SpringExtensionFactory implements ExtensionFactory {
        // ...(省略)
        
        @Override
        @SuppressWarnings("unchecked")
        public <T> T getExtension(Class<T> type, String name) {
            for (ApplicationContext context : contexts) {
            // 从Spring的ApplicationContext里获取Bean
                if (context.containsBean(name)) {
                    Object bean = context.getBean(name);
                    if (type.isInstance(bean)) {
                        return (T) bean;
                    }
                }
            }
            return null;
        }
    }
    

    借助Spring能力,从ApplicationContext里获取到需要的Bean之后,装配到Dubbo框架的实例对象中,而完全看不出Spring的痕迹,很好地与Spring解耦

    总结

    本文介绍了传统装配模式与现代注解驱动注入方式之间的区别,先是明白了传统装配模式中的byNamebyType@Resouce@Autowired等注解之间并无任何的联系,在底层处理上它们是两套逻辑,不可"见名知义"而混问一谈。接着介绍了Annotation-driven injection的方式,从中挑选了field injection进行了详细过程的分析,并澄清了@Resouce一个网上不严谨的说法。最后,对于这两种方式的使用进行了一个思考,对在什么场合使用什么方式能更了然于胸


    导读: AutowireCapableBeanFactory探密(1)——为第三方框架赋能

    相关文章

      网友评论

        本文标题:AutowireCapableBeanFactory探密(2)—

        本文链接:https://www.haomeiwen.com/subject/iyfaactx.html