Spring 注解原理(二)AutowiredAnnotationBeanPostProcessor:@Autowired @Value @Inject @Lookup


Spring 注解原理(二)AutowiredAnnotationBeanPostProcessor:@Autowired @Value @Inject @Lookup

Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10198698.html)

AutowiredAnnotationBeanPostProcessor 是 Spring 注解驅動的核心組件之一,都是處理的 Bean 的依賴注入,相關的注解有 @Autowired @Value @Inject @Lookup 四個,也可以自定義注解后添到 autowiredAnnotationTypes 集合中。

  • @Autowired @Value @Inject:這三個注解的邏輯完全一樣,都是處理依賴注入,其優先級 @Autowired > @Value > @Inject。因此本文在此做如下約定,@Autowired 一般指的是這三個注解。
  • @Lookup:本質也是解決依賴注入的問題,但和上面三個注解不同的是,@Lookup 注入的對象是動態的( 尤其是 prototype 實例),而 @Autowired 注入的對象是靜態的,一旦注入就不可發生改變。@Lookup 只能標注在抽象方法上,實例化時使用 CglibSubclassingInstantiationStrategy 進行字節碼提升,每次調用該抽象方法時,都調用 beanFactory#getBean 重新獲取對象。

1. 工作原理

現在我們先大致看一下 AutowiredAnnotationBeanPostProcessor 是如何工作的呢?beanFactory#doCreateBean 在創建 bean 過程中依次調用如下方法:

  • determineCandidateConstructors:解析類的構造器,用於處理構造器注入。如果構造器上標注有 @Autowired 注解,或只有一個有參構造器,則采用構造器自動注入。否則完全按照默認的配置參數 bd. constructorArgumentValues 實例化對象,或無參構造器實例化對象。
  • postProcessMergedBeanDefinition:配合 postProcessPropertyValues 方法一起處理字段或方法注入。解析標注有 @Autowired 的注入點元信息 InjectionMetadata,底層調用 findAutowiringMetadata 方法解析注入點元信息。
  • postProcessPropertyValues:將 postProcessMergedBeanDefinition 階段解析的 InjectionMetadata 依次進行屬性注入。
AbstractAutowireCapableBeanFactory#doCreateBean
    -> createBeanInstance
        -> determineConstructorsFromBeanPostProcessors
            -> AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
        -> autowireConstructor
            -> ConstructorResolver#autowireConstructor
    -> applyMergedBeanDefinitionPostProcessors
        -> AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
    -> populateBean
        -> AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues

2. determineCandidateConstructors

determineCandidateConstructors 處理構造器注入的場景。該方法解析類的構造器,如果構造器上標注有 @Autowired 注解,或只有一個有參構造器,則采用構造器自動注入。否則完全按照默認的配置參數 bd. constructorArgumentValues 實例化對象,或無參構造器實例化對象。

  • @Lookup:實例化時使用 CglibSubclassingInstantiationStrategy 進行字節碼提升,生成代理對象。

  • @Autowired:如果構造器上標注有 @Autowired(required=false) 注解,則添加到候選構造器 candidates 中,最后再將默認的無參構造器(如果存在)添加到 candidates 中返回即可。

    但如果有標注 @Autowired 的候選構造器,則標注有 @Autowired 注解的候選構造器只能有一個,並最終返回這個候選構造器。

  • 無 @Autowired:如果只有一個有參構造器,則直接返回這個構造器即可。如果有多個構造器或只有一個默認構造器,則返回 null。

@Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeanCreationException {
    // 1. 校驗@Lookup 注解 省略...
    // 2. 解析@Autowire標注的構造器
    Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
    if (candidateConstructors == null) {
        synchronized (this.candidateConstructorsCache) {
            candidateConstructors = this.candidateConstructorsCache.get(beanClass);
            if (candidateConstructors == null) {
                Constructor<?>[] rawCandidates = beanClass.getDeclaredConstructors();
                List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
                Constructor<?> requiredConstructor = null;
                Constructor<?> defaultConstructor = null;
                int nonSyntheticConstructors = 0;
                // 3.1 遍歷所有的構造器
                for (Constructor<?> candidate : rawCandidates) {
                    // 3.2 構造器上是否標注有@Autowire,注意CGLIB代理
                    MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
                    if (ann == null) {
                        Class<?> userClass = ClassUtils.getUserClass(beanClass);
                        if (userClass != beanClass) {
                            Constructor<?> superCtor =  userClass.getDeclaredConstructor(candidate.getParameterTypes());
                            ann = findAutowiredAnnotation(superCtor);
                        }
                    }
                    // 3.3 如果標注@Autowire,進一步判斷是否require=true,如果為true,只能有一個
                    if (ann != null) {
                        if (requiredConstructor != null) {
                            throw new BeanCreationException();
                        }
                        boolean required = determineRequiredStatus(ann);
                        if (required) {
                            if (!candidates.isEmpty()) {
                                throw new BeanCreationException();
                            }
                            requiredConstructor = candidate;
                        }
                        candidates.add(candidate);
                    // 3.4 緩存默認構造器 
                    } else if (candidate.getParameterCount() == 0) {
                        defaultConstructor = candidate;
                    }
                }
                // 3.5   結果處理
                // 3.5.1 標注@Autowire,如果require=true直接返回。否則添加無參構造器返回
                if (!candidates.isEmpty()) {
                    if (requiredConstructor == null) {
                        if (defaultConstructor != null) {
                            candidates.add(defaultConstructor);
                        }
                    }
                    candidateConstructors = candidates.toArray(new Constructor<?>[0]);
                // 3.5.2 無@Autowire。如果只有一個有參構造器,返回這個構造器,自動注入
                } else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
                    candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
                // 3.5.3 無@Autowire。如果有多個構造器或只有一個無參構造器,返回null
                } else {
                    candidateConstructors = new Constructor<?>[0];
                }
                this.candidateConstructorsCache.put(beanClass, candidateConstructors);
            }
        }
    }
    return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

說明: determineCandidateConstructors 解析類的構造器,我們主要看一下結果是如何處理的。

  1. 有構造器上標注 @Autowire。根據屬性值 require 又可以分為兩種情況,指定是否是必須的構造器,默認為 true。
    • require=true:只能有一個構造器設置為必須構造器,直接使用這個構造器實例化對象。
    • require=false:可以標注多個候選構造器,需要根據參數進一步匹配具體的構造器。此時,會添加默認的無參構造器。
  2. 沒有構造器標注 @Autowire。也可以分為兩種情況:
    • 只有一個有參構造器:直接返回這個有參構造器。
    • 有多個構造器或只有一個無參構造器:返回 null。此時需要根據 bd 配置來實例化對象。
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    ...
    // 1. 解析是否有構造器可用,當 ctors!=null 時采用即構造器自動注入
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    // 2. 有參構造器實例化(大部分情況),采用即構造器自動注入
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
        mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        return autowireConstructor(beanName, mbd, ctors, args);
    }
    // 3. 無參構造器實例化
    return instantiateBean(beanName, mbd);
}

3. postProcessMergedBeanDefinition

postProcessMergedBeanDefinition 和 postProcessMergedBeanDefinition 處理字段或方法注入的場景。postProcessMergedBeanDefinition 方法將標注 @Autowired 注入點(字段或方法)解析成元信息 InjectionMetadata,postProcessMergedBeanDefinition 則根據元信息 InjectionMetadata 注入到 bean 中。

字段或方法注入相關方法說明:

  • postProcessMergedBeanDefinition:將 @Autowired 注入點(字段或方法)解析成元信息 InjectionMetadata。
  • findAutowiringMetadata:緩存解析后的注入點元信息到 injectionMetadataCache。
  • buildAutowiringMetadata:遞歸遍歷所有的字段和方法,將標注 @Autowired 的注入點解析成 InjectionMetadata。該方法不會解析靜態字段,所以 @Autowired 無法注入靜態字段。
    • 字段:AutowiredFieldElement
    • 方法:AutowiredMethodElement
  • InjectionMetadata#checkConfigMembers:將已經處理過的注入點緩存到 bd.externallyManagedConfigMembers 中,下次再處理時不會處理已經緩存的注入點。
  • InjectionMetadata#inject:依次遍歷所有的注入點元信息 InjectedElement,進行屬性注入。
AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
    -> findAutowiringMetadata
        -> buildAutowiringMetadata
    -> InjectionMetadata#checkConfigMembers
AutowiredAnnotationBeanPostProcessor#postProcessProperties
    -> InjectionMetadata#inject

3.1 buildAutowiringMetadata

buildAutowiringMetadata 方法遞歸遍歷所有的字段和方法,將標注 @Autowired 的注入點解析成 InjectionMetadata。該方法不會解析靜態字段,所以 @Autowired 無法注入靜態字段。方法本身並不難理解,最重要的關心解析后的對象 AutowiredFieldElement 和 AutowiredMethodElement。

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    // 1. 校驗,如果clazz是JDK中的類,直接忽略,因為不可能標注有這些標注
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    }

    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;

    // 遞歸循環所有的父類,所有@Autowired父類的字段也會自動注入
    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

        // 2. 解析所有字段上的注解,封裝成AutowiredFieldElement,不包括static字段
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            MergedAnnotation<?> ann = findAutowiredAnnotation(field);
            if (ann != null) {
                if (Modifier.isStatic(field.getModifiers())) {
                    return;
                }
                boolean required = determineRequiredStatus(ann);
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });

        // 3. 解析所有方法上的注解,封裝成AutowiredMethodElement,不包括static方法
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 3.1 處理橋接方法,先忽略這部分
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                // 3.2 忽略static方法
                if (Modifier.isStatic(method.getModifiers())) {
                    return;
                }
                boolean required = determineRequiredStatus(ann);
                // 3.3 如果是JavaBean字段,則返回pd,否則返回null
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
        elements.addAll(0, currElements);
        targetClass = targetClass.getSuperclass();
    } while (targetClass != null && targetClass != Object.class);
    return InjectionMetadata.forElements(elements, clazz);
}

說明: buildAutowiringMetadata 字段和方法注入點的元信息解析大同小異:

  1. 遞歸解析所有的父類的字段和方法,所以父類可以通過 @Autowired 注入。
  2. 不會解析靜態字段或方法,所以靜態字段無法通過 @Autowired 注入。
  3. 字段和方法分別解析為 AutowiredFieldElement 和 AutowiredMethodElement。其中有兩個重要的屬性:一個是注入點 Member,二是注入點是否必須。

4. postProcessPropertyValues

postProcessMergedBeanDefinition 方法根據元信息 InjectionMetadata,在 Spring IoC 容器中查找依賴注入到 bean 中。該方法完全委托給了 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) {
            element.inject(target, beanName, pvs);
        }
    }
}

說明: inject 方法就是循環所有的注入點,依次調用其 inject 進行屬性注入。問題的關鍵是 checkedElements 是什么,也就是說會注入那些字段?在之前分析 postProcessMergedBeanDefinition 時,提到調用 findAutowiringMetadata 解析完注入點元信息后,會調用 InjectionMetadata#checkConfigMembers 校驗。校驗到底是做什么的呢?

public void checkConfigMembers(RootBeanDefinition beanDefinition) {
    Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());
    for (InjectedElement element : this.injectedElements) {
        Member member = element.getMember();
        if (!beanDefinition.isExternallyManagedConfigMember(member)) {
            beanDefinition.registerExternallyManagedConfigMember(member);
            checkedElements.add(element);
        }
    }
    this.checkedElements = checkedElements;
}

說明: bd.externallyManagedConfigMembers 緩存已經校驗過的注入點,這些緩存的注入點不會再次進行注入,目的就是為了避免重復注入的問題。那問題就來了,字段怎么會進行重復注入呢?比如 CommonAnnotationBeanPostProcessor 同樣會解析注入點的元信息,如果 @Autowired 和 @Resource 出現在同一個字段上,此時會出現重復注入的情況。

下面對 InjectionMetadata 中兩個注入點屬性進行一下說明:

  • injectedElements:所有解析的注入點元信息 InjectedElement。
  • checkedElements:需要進行屬性注入的注入元信息,剔除 bd.externallyManagedConfigMembers 已經處理的注入點。

下面會對字段注入和方法注入,分別進行分析。關鍵是如何進行依賴查找,底層最終都是調用 beanFactory#resolveDependency 方法。

5. AutowiredFieldElement

字段注入時,通常根據字段類型和字段名稱查找依賴。當然,如果你使用 @Value("#{beanName}") 時,會讀取注解中的值進行解析。核心還是 beanFactory#resolveDependency 方法。方法本身很簡單,都不多說了。

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Field field = (Field) this.member;
    Object value;
    // 從緩存中提取value值,可能為desc、beanName、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);
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        synchronized (this) {
            // 只要是解析過,不會緩存下來
            if (!this.cached) {
                if (value != null || this.required) {
                    // 緩存DependencyDescriptor
                    this.cachedFieldValue = desc;
                    registerDependentBeans(beanName, autowiredBeanNames);
                    // 緩存名稱beanName,直接根據名稱查找
                    if (autowiredBeanNames.size() == 1) {
                        String autowiredBeanName = autowiredBeanNames.iterator().next();
                        if (beanFactory.containsBean(autowiredBeanName) &&
                            beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                            this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                desc, autowiredBeanName, field.getType());
                        }
                    }
                } else {
                    this.cachedFieldValue = null;
                }
                this.cached = true;
            }
        }
    }
    // java反射
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
    }
}

6. AutowiredMethodElement

基本上和 AutowiredFieldElement 雷同,唯一不同的是方法注入時,需要對方法的所以參數依次調用 beanFactory#resolveDependency 進行依賴查找。根據 require 值,如果為 true 時無法查找到依賴時會繼續查找,false 則不再進行查找。一般默認為 true。


每天用心記錄一點點。內容也許不重要,但習慣很重要!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM