AutowireCapableBeanFactory探密(2)——传统装配模式与现代注解驱动注入方式

回顾

上篇,介绍了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)——为第三方框架赋能

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341