Spring IoC 依賴注入(三)resolveDependency

Spring IoC 依賴注入(三)resolveDependency

[toc]

resolveDependency 是 Spring 進行依賴查找的核心 API。弄明白了 resolveDependency,基本上依賴注入的問題也就搞明白了一半。resolveDependency 本質是根據類型查找依賴,調用 beanFactory#beanNamesForType 方法根據類型查找依賴名稱。

  1. 根據名稱查找依賴:getBean(beanName)。
  2. 根據類型查找依賴:beanFactory#resolveDependency,本文會深入分析依賴查找的源碼。

認知一下,與依賴查找的相關 API:

  1. resolveDependency:支持 Optional、延遲注入、懶加載注入、正常注入。

  2. doResolveDependency:在依賴查找之前,想辦法快速查找,如緩存 beanName、@Value 等直接獲取注入的值,避免通過類型查找,最後纔對集合依賴和單一依賴分別進行了處理。實際上,無論是集合依賴還是單一依賴查找都是調用 findAutowireCandidates 方法。

  3. findAutowireCandidates:真正在 Spring IoC 容器中進行依賴查找,依賴查找的來源有三:①內部對象 ②託管Bean ③BeanDefinition。最後如果無法查找到依賴對象,會進行一些補償機制,想方設法獲取注入的對象,如泛型補償,自引用補償。

  4. isAutowireCandidate:判斷候選對象是否可用,有三重過濾規則:①bd.autowireCandidate=true -> ②泛型匹配 -> ③@Qualifier。委託給 ContextAnnotationAutowireCandidateResolver。

    isAutowireCandidate 方法過濾候選對象

首先,我們也先認知一下這幾個類:

  1. ParameterNameDiscoverer:用於提取方法參數名稱。
  2. DependencyDescriptor:封裝了依賴注入點的詳細信息,可以是字段 Field,也可以是方法參數 MethodParameter。
  3. AutowireCandidateResolver:判斷 DependencyDescriptor 是否是可注入對象。Spring IoC 容器默認實現爲 ContextAnnotationAutowireCandidateResolver。

1. resolveDependency

resolveDependency 依賴查找解決了以下場景:

  1. Optional:JDK8 提供了 API。主要是將依賴設置非強制依賴,即 descriptor.required=false。
  2. 延遲依賴注入支持:ObjectFactory、ObjectProvider、javax.inject.Provider 沒有本質的區別。
  3. 另一種延遲注入的支持 - @Lazy 屬性。
  4. 根據類型查找依賴 - doResolveDependency。
@Override
public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName, Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    // ParameterNameDiscovery用於解析方法參數名稱
    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    // 1. Optional<T>
    if (Optional.class == descriptor.getDependencyType()) {
        return createOptionalDependency(descriptor, requestingBeanName);
    // 2. ObjectFactory<T>、ObjectProvider<T>
    } else if (ObjectFactory.class == descriptor.getDependencyType() ||
             ObjectProvider.class == descriptor.getDependencyType()) {
        return new DependencyObjectProvider(descriptor, requestingBeanName);
    // 3. javax.inject.Provider<T>
    } else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
        return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    } else {
        // 4. @Lazy
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
            descriptor, requestingBeanName);
        // 5. 正常情況
        if (result == null) {
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}

<b>說明:</b> 前四種場景(Optional,延遲注入 ObjectProvider + @Lazy),我們先放一下,重點分析一下最基本的使用場景,Spring 是如何進行依賴查找的 - doResolveDependency。其實無論是什麼場景,最底層都是調用 doResolveDependency。

2. doResolveDependency

doResolveDependency 封裝了依賴查找的各種情況:

  1. 快速查找: @Autowired 註解處理場景。AutowiredAnnotationBeanPostProcessor 處理 @Autowired 註解時,如果注入的對象只有一個,會將該 bean 對應的名稱緩存起來,下次直接通過名稱查找會快很多。
  2. 注入指定值:@Value 註解處理場景。QualifierAnnotationAutowireCandidateResolver 處理 @Value 註解時,會讀取 @Value 對應的值進行注入。如果是 String 要經過三個過程:①佔位符處理 -> ②EL 表達式解析 -> ③類型轉換,這也是一般的處理過程,BeanDefinitionValueResolver 處理 String 對象也是這個過程。
  3. 集合依賴查詢:直接全部委託給 resolveMultipleBeans 方法。
  4. 單個依賴查詢:先調用 findAutowireCandidates 查找所有可用的依賴,如果有多個依賴,則根據規則匹配: @Primary -> @Priority -> ③方法名稱或字段名稱。
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {

    InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
    try {
        // 1. 快速查找,根據名稱查找。AutowiredAnnotationBeanPostProcessor用到
        Object shortcut = descriptor.resolveShortcut(this);
        if (shortcut != null) {
            return shortcut;
        }

        // 2. 注入指定值,QualifierAnnotationAutowireCandidateResolver解析@Value會用到
        Class<?> type = descriptor.getDependencyType();
        Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
        if (value != null) {
            if (value instanceof String) {
                // 2.1 佔位符解析
                String strVal = resolveEmbeddedValue((String) value);
                BeanDefinition bd = (beanName != null && containsBean(beanName) ?
                                     getMergedBeanDefinition(beanName) : null);
                // 2.2 Spring EL 表達式
                value = evaluateBeanDefinitionString(strVal, bd);
            }
            TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
            try {
                // 2.3 類型轉換
                return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
            } catch (UnsupportedOperationException ex) {
                return (descriptor.getField() != null ?
                        converter.convertIfNecessary(value, type, descriptor.getField()) :
                        converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
            }
        }

        // 3. 集合依賴,如 Array、List、Set、Map。內部查找依賴也是使用findAutowireCandidates
        Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
        if (multipleBeans != null) {
            return multipleBeans;
        }

        // 4. 單個依賴查詢
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
        // 4.1 沒有查找到依賴,判斷descriptor.require
        if (matchingBeans.isEmpty()) {
            if (isRequired(descriptor)) {
                raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
            }
            return null;
        }

        String autowiredBeanName;
        Object instanceCandidate;

        // 4.2 有多個,如何過濾
        if (matchingBeans.size() > 1) {
            // 4.2.1 @Primary -> @Priority -> 方法名稱或字段名稱匹配 
            autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
            // 4.2.2 根據是否必須,拋出異常。注意這裏如果是集合處理,則返回null
            if (autowiredBeanName == null) {
                if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
                    return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
                } else {
                    return null;
                }
            }
            instanceCandidate = matchingBeans.get(autowiredBeanName);
        } else {
            // We have exactly one match.
            Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
            autowiredBeanName = entry.getKey();
            instanceCandidate = entry.getValue();
        }

        // 4.3 到了這,說明有且僅有命中一個
        if (autowiredBeanNames != null) {
            autowiredBeanNames.add(autowiredBeanName);
        }
        // 4.4 實際上調用 getBean(autowiredBeanName, type)。但什麼情況下會出現這種場景?
        if (instanceCandidate instanceof Class) {
            instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
        }
        Object result = instanceCandidate;
        if (result instanceof NullBean) {
            if (isRequired(descriptor)) {
                raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
            }
            result = null;
        }
        if (!ClassUtils.isAssignableValue(type, result)) {
            throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
        }
        return result;
    } finally {
        ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
    }
}

<b>說明:</b> doResolveDependency 方法的四個功能,快速查找和集合處理都委託給了其它方法,注入指定值雖然看起來複雜,但佔位符處理、EL 表達式解析、類型轉換這三個功能點都有具體的類處理,也不是本文的重點。

我們重點看一下單個依賴的查詢,弄明白了單個依賴的查詢,其它集合依賴也差不多。

  1. 查找容器中所有可用依賴:findAutowireCandidates 方法根據類型查找依賴。
  2. 如何有多個依賴怎麼處理?其實 Spring 有一套通用的流程,先按 @Primary 查找,再按 @Priority,最後按方法名稱或字段名稱查找,直到只有一個 bean 爲止。相關的匹配規則見 determineAutowireCandidate 方法。
  3. 此時只有一個依賴,從容器獲取真實的 bean。descriptor.resolveCandidate 方法根據名稱 autowiredBeanName 實例化對象。

思考:findAutowireCandidates 返回的爲什麼是對象類型,而不是實例對象?

Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);

matchingBeans 中的 Object 對象可能是對象類型,而不全部是實例對象。因爲 findAutowireCandidates 方法是根據類型 type 查找名稱 beanNames,如果容器中該 beanName 還沒有實例化,findAutowireCandidates 不會畫蛇添足直接實例化該 bean,當然如果已經實例化了會直接返回這個 bean。

3. findAutowireCandidates

根據上面的分析,resolveDependency 方法對 Optional、延遲注入、懶加載注入等分別進行了處理。之後 doResolveDependency 在正式查找之前看能不能快速查找,如緩存 beanName、@Value 等快速指定需要注入的值,避免通過類型查找,最後纔對集合依賴和單一依賴分別進行了處理。實際上,無論是集合依賴還是單一依賴查找,本質上都是調用 findAutowireCandidates 進行類型依賴查找。

從 findAutowireCandidates 方法,我們可以看到 Spring IoC 依賴注入的來源:

  1. 先查找 Spring IoC 內部依賴 resolvableDependencies。在 AbstractApplicationContext#prepareBeanFactory 方法中默認設置瞭如下內部依賴:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext。
  2. 在父子容器進行類型查找:查找類型匹配的 beanNames,beanFactory#beanNamesForType 方法根據類型查找是,先匹配單例實例類型(包括 Spring 託管 Bean),再匹配 BeanDefinition 的類型。從這一步,我們可以看到 Spring 依賴注入的另外兩個來源:一是 Spring 託管的外部 Bean,二是 Spring BeanDefinition。
protected Map<String, Object> findAutowireCandidates(
    @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
    
    Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
    // 1. Spring IoC 內部依賴 resolvableDependencies
    for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
        Class<?> autowiringType = classObjectEntry.getKey();
        if (autowiringType.isAssignableFrom(requiredType)) {
            Object autowiringValue = classObjectEntry.getValue();
            autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
            if (requiredType.isInstance(autowiringValue)) {
                result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                break;
            }
        }
    }
    
    // 2. 類型查找:本質上遞歸調用beanFactory#beanNamesForType。先匹配實例類型,再匹配bd。
    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
        this, requiredType, true, descriptor.isEager());
    for (String candidate : candidateNames) {
        // 2.1 isSelfReference說明beanName和candidate本質是同一個對象
        //     isAutowireCandidate進一步匹配bd.autowireCandidate、泛型、@@Qualifier等進行過濾
        if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
            // 2.2 添加到候選對象中
            addCandidateEntry(result, candidate, descriptor, requiredType);
        }
    }
    
    // 3. 補償機制:如果依賴查找無法匹配,怎麼辦?包含泛型補償和自身引用補償兩種。
    if (result.isEmpty()) {
        boolean multiple = indicatesMultipleBeans(requiredType);
        // 3.1 fallbackDescriptor: 泛型補償,實際上是允許注入對象類型的泛型存在無法解析的情況
        DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
        // 3.2 補償1:不允許自稱依賴,但如果是集合依賴,需要過濾非@Qualifier對象。什麼場景?
        for (String candidate : candidateNames) {
            if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
                (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
                addCandidateEntry(result, candidate, descriptor, requiredType);
            }
        }
        // 3.3 補償2:允許自稱依賴,但如果是集合依賴,注入的集合依賴中需要過濾自己
        if (result.isEmpty() && !multiple) {
            for (String candidate : candidateNames) {
                if (isSelfReference(beanName, candidate) &&
                    (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
                    isAutowireCandidate(candidate, fallbackDescriptor)) {
                    addCandidateEntry(result, candidate, descriptor, requiredType);
                }
            }
        }
    }
    return result;
}

<b>說明:</b> findAutowireCandidates 大致可以分爲三步:先查找內部依賴,再根據類型查找,最後沒有可注入的依賴則進行補償。

  1. 查找內部依賴:Spring IoC 容器本身相關依賴,這部分內容是用戶而言是透明的,也不用感知。resolvableDependencies 集合中註冊如 BeanFactory、ApplicationContext 、ResourceLoader、ApplicationEventPublisher 等。
  2. 根據類型查找:包括 ①外部託管 Bean ②註冊 BeanDefinition。類型查找調用 beanFactory#beanNamesForType 方法,詳見 Spring IoC 依賴查找之類型自省。我們來看一下如何過濾的。
    • 自身引用:isSelfReference 方法判斷 beanName 和 candidate 是否是同一個對象,包括兩種情況:一是名稱完全相同,二是 candidate 對應的工廠對象創建了 beanName。
    • 是否可以注入:底層實際調用 resolver.isAutowireCandidate 方法進行過濾,包含三重規則:①bd.autowireCandidate=true -> ②泛型匹配 -> ③@Qualifier。下面會詳細介紹這個方法。
  3. 補償機制:如果依賴查找無法匹配,怎麼辦?Spring 提供了兩種補償機制:一是泛型補償,允許注入對象對象的泛型無法解析,二是自身引用補償,對這兩種機制使用如下:
    • 先使用泛型補償,不允許自身引用:即 fallbackDescriptor。此時如果是集合依賴,對象必須是 @Qualifier 類型。
    • 允許泛型補償和自身引用補償:但如果是集合依賴,必須過濾自己本身,即 beanName.equals(candidate) 必須剔除。

現在 findAutowireCandidates 處理過程,基本上很清晰了,還有兩個小問題需要再澄清一下:

  1. isAutowireCandidate 方法是如何過濾修改對象?
  2. addCandidateEntry 最終最終返回的都是實例對象嗎?

先看一下 addCandidateEntry 方法,如果對象還未實例化,Spring 不會畫蛇添足將 candidateName 通過 getName 提前實例化。之所以要強調這點,是因爲 <b style='color:red'>Spring 的 Bean 生命週期,其實從 Bean 還未實例化就已經開始,Spring 會盡可能的不要初始化該 Bean,除非顯式調用 getBean 或不得不實例化時</b>,這點在閱讀源碼是會感受非常強烈,我們在使用 Spring API 時也要非常注意這點。

private void addCandidateEntry(Map<String, Object> candidates, String candidateName,
                               DependencyDescriptor descriptor, Class<?> requiredType) {

    // 1. 集合依賴,直接調用 getName(candidateName) 實例化
    if (descriptor instanceof MultiElementDescriptor) {
        Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
        if (!(beanInstance instanceof NullBean)) {
            candidates.put(candidateName, beanInstance);
        }
    // 2. 已經實例化,直接返回實例對象
    } else if (containsSingleton(candidateName) || (descriptor instanceof StreamDependencyDescriptor && ((StreamDependencyDescriptor) descriptor).isOrdered())) {
        Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
        candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance));
    // 3. 只獲取candidateName的類型,真正需要注入時才實例化對象
    } else {
        candidates.put(candidateName, getType(candidateName));
    }
}

<b>說明:</b> descriptor.resolveCandidate 基本上都是直接調用 getName(beanName) 實例化 bean。在大部分場景中,addCandidateEntry 方法只會以返回該 candidateName 對應的類型,而不會提前實例該對象。

4. isAutowireCandidate

isAutowireCandidate 判斷候選對象是否可用。實際是都是委託給 AutowireCandidateResolver#isAutowireCandidate 接口判斷,Spring 中默認的實現是 ContextAnnotationAutowireCandidateResolver。

isAutowireCandidate 方法過濾候選對象有三重規則:①bd.autowireCandidate=true -> ②泛型匹配 -> ③@Qualifier。更多源碼分析見 Spring 註解原理 AutowireCandidateResolver:@Qualifier @Value

protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd,
        DependencyDescriptor descriptor, AutowireCandidateResolver resolver) {
    String beanDefinitionName = BeanFactoryUtils.transformedBeanName(beanName);
    // 1. 傳統方式:解析 bd.beanClass,注意 Spring註解驅動時根本不會配置beanClassName
    resolveBeanClass(mbd, beanDefinitionName);
    // 2. 註解驅動:解析工廠方法 bd.factoryMethodToIntrospect
    if (mbd.isFactoryMethodUnique && mbd.factoryMethodToIntrospect == null) {
        new ConstructorResolver(this).resolveFactoryMethodIfPossible(mbd);
    }
    // 3. 直接委託給AutowireCandidateResolver
    return resolver.isAutowireCandidate(
        new BeanDefinitionHolder(mbd, beanName, getAliases(beanDefinitionName)), descriptor);
}

<b>說明:</b> 主要注意一下傳統方式和註解驅動獲取 Bean 類型的不同:

  • 傳統方式:配置 beanClassName,直接解析成 beanClass,從而獲取對象類型。
  • 註解驅動:如 @Bean 方式,需要解析方法返回值類型,獲取對象類型。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章