Spring 原始碼閱讀 41:AutowiredAnnotationBeanPostProcessor 分析(2)
持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第12天,點選檢視活動詳情
基於 Spring Framework v5.2.6.RELEASE
概述
上一篇介紹了 AutowiredAnnotationBeanPostProcessor 後處理器中的determineCandidateConstructors
。這個方法為 Spring 通過反射建立 Bean 例項是提供了候選的構造方法。
本文開始分析 AutowiredAnnotationBeanPostProcessor 中另一個比較重要的處理方法postProcessMergedBeanDefinition
,它被呼叫的時機是在 Spring 通過反射建立 Bean 例項物件之後、屬性裝配之前。它的作用,是將類中標記了相關注解的注入點解析出來。
postProcessMergedBeanDefinition
方法分析
進入postProcessMergedBeanDefinition
方法的原始碼。
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
非常簡潔,只有兩行程式碼,分別呼叫了兩個方法,我們逐個分析。
findAutowiringMetadata
先進入findAutowiringMetadata
方法。
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
metadata = buildAutowiringMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}
從方法的名稱中可以看出,它用來從類中找到自動注入元資訊,最後返回一個 InjectionMetadata 型別的結果。
首先,會從快取injectionMetadataCache
中,查詢注入元資訊,如果獲取到的注入元資訊不需要被重新整理,則直接返回。如果需要重新整理,則通過buildAutowiringMetadata
方法,構建元資訊,並放入快取中,再在方法末尾返回。
在這裡,如果快取中獲取到的注入元資訊為空,也屬於需要重新整理的情況。
接下來我們看buildAutowiringMetadata
方法是如何構建這些元資訊的。
這個方法比較長,我們逐步來分析。
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
首先會判斷當前的型別是不是 Java 內部的型別,如果是的話,則直接返回空的資訊。
```
List
do { // 迴圈語句塊 } while (targetClass != null && targetClass != Object.class); ```
接著聲明瞭一個 InjectionMetadata.InjectedElement 型別的空列表,以及一個表示型別的targetClass
變數,初始值為方法引數傳入的clazz
。然後,在targetClass
不為空且不是 Object 型別的情況下,執行do-while
迴圈中的邏輯。
下面我們看do-while
語句塊中的內容。
```
final List
ReflectionUtils.doWithLocalFields(targetClass, field -> { MergedAnnotation<?> ann = findAutowiredAnnotation(field); if (ann != null) { if (Modifier.isStatic(field.getModifiers())) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation is not supported on static fields: " + field); } return; } boolean required = determineRequiredStatus(ann); currElements.add(new AutowiredFieldElement(field, required)); } }); ```
在迴圈語句塊中,首先會遍歷所有的被標記了@AutoWired
、@Value
、@Inject
註解的字屬性欄位封裝為 AutowiredFieldElement 物件,並新增到事先宣告好的currElements
集合中。需要注意的是,這裡會跳過靜態欄位,因此靜態欄位是無法通過這幾個註解進行注入的。
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
}
return;
}
if (method.getParameterCount() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation should only be used on methods with parameters: " +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
接著,再將添加了這些註解的非晶態方法,封裝成 AutowiredMethodElement 物件,新增到currElements
集合中。
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
在迴圈體的最後,將currElements
中的內容都新增到elements
集合中,然後將targetClass
指向其父類。
這裡結合迴圈體的while
條件,可以知道,這個迴圈體要執行的工作是,以方法引數傳入的型別開始遍歷它的父類,一直到 Object 類之前,將這些類中宣告的所有帶注入相關注解的非靜態的屬性和方法都封裝成 InjectionMetadata.InjectedElement 物件(屬性對應 AutowiredFieldElement,方法對應 AutowiredMethodElement),然後統一放到elements
集合中。
return InjectionMetadata.forElements(elements, clazz);
方法的最後,將elements
集合和型別資訊封裝成 InjectionMetadata 返回。
checkConfigMembers
回到postProcessMergedBeanDefinition方法,在得到 InjectionMetadata 型別的註解資訊metadata
後,會呼叫它的checkConfigMembers
方法。
// org.springframework.beans.factory.annotation.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);
if (logger.isTraceEnabled()) {
logger.trace("Registered injected element on class [" + this.targetClass.getName() + "]: " + element);
}
}
}
this.checkedElements = checkedElements;
}
這個方法中,會對injectedElements
成員變數集合進行遍歷,這裡 InjectionMetadata 的injectedElements
的成員變數,其實就是上一部中建立 InjectionMetadata 物件時,傳入的elements
引數,也就是解析出的帶注入註解的屬性和方法封裝後的物件集合。
迴圈中會判斷當前遍歷到的element
,在 BeanDefinition 中是不是外部管理的配置成員,這裡的成員指的就是屬性或者方法,如果不是的話,則將其註冊為外部管理的配置成員,也就是新增到 BeanDefinition 的externallyManagedConfigMembers
集合中,然後再將element
物件新增到事先宣告好的 InjectedElement 集合checkedElements
中。
方法的最後,將checkedElements
集合賦值給 InjectionMetadata 的checkedElements
成員變數。
總結
本文介紹了 AutowiredAnnotationBeanPostProcessor 後處理器中的postProcessMergedBeanDefinition
方法,它的作用是在當前處理的型別中解析出需要注入的屬性和方法。
下一篇講分析 AutowiredAnnotationBeanPostProcessor 最後一個比較重要的處理方法postProcessProperties
。
- Spring 原始碼閱讀 42:AutowiredAnnotationBeanPostProcessor 分析(3)
- Spring 原始碼閱讀 41:AutowiredAnnotationBeanPostProcessor 分析(2)
- Kafka 消費者組 Rebalance 詳細過程
- Spring 原始碼閱讀 01:Resource 資源抽象
- 初識機器學習:迴歸分析
- 初識機器學習:Louvain 社群發現演算法
- 初識機器學習:關聯規則
- 使用 Redis 實現分散式鎖的方法
- Kafka 目錄裡的指令碼那麼多,它們都是用來幹什麼的?
- Kafka 消費者組位移重設的幾種方式
- LeetCode - 84. 柱狀圖中最大的矩形
- LeetCode - 22. 括號生成