Spring IoC 依賴注入(三)resolveDependency
[toc]
resolveDependency 是 Spring 進行依賴查找的核心 API。弄明白了 resolveDependency,基本上依賴注入的問題也就搞明白了一半。resolveDependency 本質是根據類型查找依賴,調用 beanFactory#beanNamesForType 方法根據類型查找依賴名稱。
- 根據名稱查找依賴:getBean(beanName)。
- 根據類型查找依賴:beanFactory#resolveDependency,本文會深入分析依賴查找的源碼。
認知一下,與依賴查找的相關 API:
-
resolveDependency:支持 Optional、延遲注入、懶加載注入、正常注入。
-
doResolveDependency:在依賴查找之前,想辦法快速查找,如緩存 beanName、@Value 等直接獲取注入的值,避免通過類型查找,最後纔對集合依賴和單一依賴分別進行了處理。實際上,無論是集合依賴還是單一依賴查找都是調用 findAutowireCandidates 方法。
-
findAutowireCandidates:真正在 Spring IoC 容器中進行依賴查找,依賴查找的來源有三:①內部對象 ②託管Bean ③BeanDefinition。最後如果無法查找到依賴對象,會進行一些補償機制,想方設法獲取注入的對象,如泛型補償,自引用補償。
-
isAutowireCandidate:判斷候選對象是否可用,有三重過濾規則:①bd.autowireCandidate=true -> ②泛型匹配 -> ③@Qualifier。委託給 ContextAnnotationAutowireCandidateResolver。
isAutowireCandidate 方法過濾候選對象
首先,我們也先認知一下這幾個類:
- ParameterNameDiscoverer:用於提取方法參數名稱。
- DependencyDescriptor:封裝了依賴注入點的詳細信息,可以是字段 Field,也可以是方法參數 MethodParameter。
- AutowireCandidateResolver:判斷 DependencyDescriptor 是否是可注入對象。Spring IoC 容器默認實現爲 ContextAnnotationAutowireCandidateResolver。
1. resolveDependency
resolveDependency 依賴查找解決了以下場景:
- Optional:JDK8 提供了 API。主要是將依賴設置非強制依賴,即 descriptor.required=false。
- 延遲依賴注入支持:ObjectFactory、ObjectProvider、javax.inject.Provider 沒有本質的區別。
- 另一種延遲注入的支持 - @Lazy 屬性。
- 根據類型查找依賴 - 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 封裝了依賴查找的各種情況:
- 快速查找: @Autowired 註解處理場景。AutowiredAnnotationBeanPostProcessor 處理 @Autowired 註解時,如果注入的對象只有一個,會將該 bean 對應的名稱緩存起來,下次直接通過名稱查找會快很多。
- 注入指定值:@Value 註解處理場景。QualifierAnnotationAutowireCandidateResolver 處理 @Value 註解時,會讀取 @Value 對應的值進行注入。如果是 String 要經過三個過程:①佔位符處理 -> ②EL 表達式解析 -> ③類型轉換,這也是一般的處理過程,BeanDefinitionValueResolver 處理 String 對象也是這個過程。
- 集合依賴查詢:直接全部委託給 resolveMultipleBeans 方法。
- 單個依賴查詢:先調用 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 表達式解析、類型轉換這三個功能點都有具體的類處理,也不是本文的重點。
我們重點看一下單個依賴的查詢,弄明白了單個依賴的查詢,其它集合依賴也差不多。
- 查找容器中所有可用依賴:findAutowireCandidates 方法根據類型查找依賴。
- 如何有多個依賴怎麼處理?其實 Spring 有一套通用的流程,先按 @Primary 查找,再按 @Priority,最後按方法名稱或字段名稱查找,直到只有一個 bean 爲止。相關的匹配規則見 determineAutowireCandidate 方法。
- 此時只有一個依賴,從容器獲取真實的 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 依賴注入的來源:
- 先查找 Spring IoC 內部依賴 resolvableDependencies。在 AbstractApplicationContext#prepareBeanFactory 方法中默認設置瞭如下內部依賴:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext。
- 在父子容器進行類型查找:查找類型匹配的 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 大致可以分爲三步:先查找內部依賴,再根據類型查找,最後沒有可注入的依賴則進行補償。
- 查找內部依賴:Spring IoC 容器本身相關依賴,這部分內容是用戶而言是透明的,也不用感知。resolvableDependencies 集合中註冊如 BeanFactory、ApplicationContext 、ResourceLoader、ApplicationEventPublisher 等。
- 根據類型查找:包括 ①外部託管 Bean ②註冊 BeanDefinition。類型查找調用 beanFactory#beanNamesForType 方法,詳見 Spring IoC 依賴查找之類型自省。我們來看一下如何過濾的。
- 自身引用:isSelfReference 方法判斷 beanName 和 candidate 是否是同一個對象,包括兩種情況:一是名稱完全相同,二是 candidate 對應的工廠對象創建了 beanName。
- 是否可以注入:底層實際調用 resolver.isAutowireCandidate 方法進行過濾,包含三重規則:①bd.autowireCandidate=true -> ②泛型匹配 -> ③@Qualifier。下面會詳細介紹這個方法。
- 補償機制:如果依賴查找無法匹配,怎麼辦?Spring 提供了兩種補償機制:一是泛型補償,允許注入對象對象的泛型無法解析,二是自身引用補償,對這兩種機制使用如下:
- 先使用泛型補償,不允許自身引用:即 fallbackDescriptor。此時如果是集合依賴,對象必須是 @Qualifier 類型。
- 允許泛型補償和自身引用補償:但如果是集合依賴,必須過濾自己本身,即 beanName.equals(candidate) 必須剔除。
現在 findAutowireCandidates 處理過程,基本上很清晰了,還有兩個小問題需要再澄清一下:
- isAutowireCandidate 方法是如何過濾修改對象?
- 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 方式,需要解析方法返回值類型,獲取對象類型。
每天用心記錄一點點。內容也許不重要,但習慣很重要!