Spring源碼分析8、applyMergedBeanDefinitionPostProcessors源碼分析

引入

在前面的文章中, 咱們講解到Spring在真正建立一個bean的時候是經過調用一個lamada表達式中的createBean
方法來建立的, 而且咱們以前也對這個createBean方法中的resolveBeforeInstantiation方法進行了詳細的
講解(完成SpringAOP的初始化工做, 設置了不須要參與AOP的類), 日後咱們又經過createBean中的doCreateBean
方法對Spring建立bean實例的源碼進行了分析, 即createBeanInstance方法, 經過上面的流程, Spring已經
建立好了bean實例, 在doCreateBean方法中, Spring調用完createBeanInstance方法後又調用了一次後置處
理器, 即咱們本篇內容須要講解的方法-applyMergedBeanDefinitionPostProcessors
複製代碼

applyMergedBeanDefinitionPostProcessors源碼分析

  • applyMergedBeanDefinitionPostProcessors
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
  for (BeanPostProcessor bp : getBeanPostProcessors()) {
    if (bp instanceof MergedBeanDefinitionPostProcessor) {
      MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
      bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
    }
  }
}

分析:
  根據代碼能夠看到, Spring其實就是調用了全部類型爲MergedBeanDefinitionPostProcessor後置處理器的
  postProcessMergedBeanDefinition方法, 在沒有手動的添加bean的後置處理器的條件下, 筆者分析發現,
  實現了該方法的後置處理器有如下幾個:
  ApplicationListenerDetector
  ScheduledAnnotationBeanPostProcessor
  RequiredAnnotationBeanPostProcessor
  InitDestroyAnnotationBeanPostProcessor
  CommonAnnotationBeanPostProcessor
  AutowiredAnnotationBeanPostProcessor

  通過一個個的源碼查看, 發現ScheduledAnnotationBeanPostProcessor和RequiredAnnotationBeanPostProcessor
  兩個後置處理器都僅僅是對postProcessMergedBeanDefinition方法進行了空實現, 而ApplicationListenerDetector
  雖然進行了實現, 可是其裏面代碼就一行:
    this.singletonNames.put(beanName, beanDefinition.isSingleton());
  即將一個bean是不是單例設置到了singletonNames這個map中, 而這個map是在ApplicationListenerDetector
  中的, 因此對於前三個後置處理器來講, 咱們能夠跳過了....

  在真正分析後三個後置處理器以前, 咱們首先從總體上說一下, 後三個後置處理器的工做都差很少, 都是爲了
  查找出知足條件的屬性、方法, 將他們封裝起來, 以便後面在填充屬性的時候能夠直接使用, 在分析源碼以前,
  咱們先來聊聊幾個源碼中會出現的類的做用
複製代碼

Member、InjectedElement、InjectionMetadata

在Java反射中, 咱們會常常的遇到Field、Method、Constructor類, 而本次咱們提到的第一個類就是Member,
該類就是上面幾個類的父類, Spring爲了可以使得獲取到的方法、屬性都放在一個地方, 採用了接口編程, 將其
都變成了Member類型

當Spring掃描到一個方法加了@Autowired的時候, 就會將該方法反射得到到Method變爲一個Member, 而後將其
放到InjectedElement中, 換句話說, InjectedElement就是對一個方法或者屬性的一個封裝, 除了有Member
存儲原始的反射信息外, 還會有額外的信息, 好比required屬性, 表示是不是必須注入的

一個類中可能會有不少個方法、屬性被標註了@Autowired註解, 那麼每個被標註的方法、屬性都用一個
InjectedElement表示, 而全部這些InjectedElement均被放入到一個Collections中, 這個集合則存在於
InjectionMetadata中, 即InjectionMetadata中的Collection<InjectedElement> injectedElements存儲
了全部須要被注入的信息, 裏面有一個targetClass屬性則是存儲了這些方法、屬性所在的類Class對象
public class InjectionMetadata {
	private final Class<?> targetClass;

	private final Collection<InjectedElement> injectedElements;

  private volatile Set<InjectedElement> checkedElements;
}

能夠看到, 在InjectionMetadata中還有一個checkedElements, 裏面也是存儲了InjectedElement, 以前提到
injectedElements的時候, 有人可能會認爲, 難道Spring在後面進行屬性填充的時候, 就是取injectedElements
中的一個個InjectedElement進行反射操做進行注入的嗎, 其實不是的, Spring實際取的是checkedElements中
的InjectedElement, 在MergedBeanDefinitionPostProcessor中postProcessMergedBeanDefinition方法
中, Spring主要是找到全部被@PostConstruct@PreDestory@Autowired@Resource@Value標註的屬性
或者方法, 將其封裝成一個個的InjectedElement, 最後放到一個新建立的InjectedMetada中, 完成這些工做
後, Spring又會通過一些判斷, 最終將這些InjectedElement從injectedElements取出來放到checkedElements
中, 在進行屬性填充的時候, Spring就會取出一個個的InjectedElement, 經過反射的方式完成屬性填充, 那麼
上述提到的三個後置處理器有什麼做用呢, 其實這是一個策略模式的典型應用, Spring對@PostConstruct@PreDestory註解的處理(轉爲InjectedElement)用的InitDestroyAnnotationBeanPostProcessor, 而對
@Resource的處理用的CommonAnnotationBeanPostProcessor, 對於@Autowired以及@Value的處理則是用的
AutowiredAnnotationBeanPostProcessor, 不一樣的後置處理器處理不一樣的註解, 下面咱們以@Autowired註解
的處理爲例子進行講解, 其它註解的處理的代碼跟這個是相似的, 就再也不進行展開了
複製代碼

AutowiredAnnotationBeanPostProcessor的postProcessMergedBeanDefinition方法

  • postProcessMergedBeanDefinition方法
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
  InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
  metadata.checkConfigMembers(beanDefinition);
}

分析:
  findAutowiringMetadata方法完成了@Autowired註解的處理, 將被該註解標註的屬性、方法封裝爲一個個的
  InjectedElement, 而後放入到InjectionMetadata中的集合injectedElements中

  checkConfigMembers方法則將injectedElements中的一個個InjectedElement取出來, 進行一些判斷, 最
  後放入到checkedElements這個Set中(屬性注入的時候就是取得checkedElements中得InjectedElement)

  下面咱們分別來說解下這兩個方法
複製代碼
  • findAutowiringMetadata
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
  String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
  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;
}

分析:
  Spring會先從緩存injectionMetadataCache中獲取當前bean對應得注入元數據, 若是needsRefresh返回了
  true, 那麼Spring就會調用buildAutowiringMetadata方法開始構建注入元數據, 構建完成後就會將其放入
  到緩存injectionMetadataCache中了

  needsRefresh的判斷很簡單, 即metadata == null || metadata.targetClass != clazz
  當metadata不存在於緩存的時候確定是要進行構建的, 因爲findAutowiringMetadata方法會在屬性注入的時
  候也被調用, 因此一般狀況下會拿到緩存中的數據, 須要注意的是, 在上述後置處理器調用完成後, 若是程序
  員手動的修改了InjectedMetadata中的targetClass, 那麼就不能用原來的元數據了, 而是要從新構建一次,
  這也是metadata.targetClass != clazz返回true的狀況下Spring也會調用構建方法的緣由
複製代碼
  • buildAutowiringMetadata
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
  List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
  Class<?> targetClass = clazz;

  do {
    final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

    ..........獲取一個個的InjectedElement..........

    elements.addAll(0, currElements);
    targetClass = targetClass.getSuperclass();
  }
  while (targetClass != null && targetClass != Object.class);

  return new InjectionMetadata(clazz, elements);
}

分析:
  能夠看到, Spring會建立一個elements的list來存放全部須要被注入的數據, 而後在while循環中, 開始獲取
  該targetClass中的元數據, 獲取完成後放入到elements中, 以後開始獲取targetClass的父類中素有須要
  被注入的數據, 直到Object爲止

  當一個類及其全部的祖先類中的元數據被掃描完成後, Spring就會將其放入到InjectionMetadata中返回,
  接下來咱們開始分析Spring在這個while循環是如何獲取一個個的InjectedElement的
複製代碼
  • 代碼一
ReflectionUtils.doWithLocalFields(targetClass, field -> {
  AnnotationAttributes ann = findAutowiredAnnotation(field);
  if (ann != null) {
    if (Modifier.isStatic(field.getModifiers())) {
      return;
    }
    boolean required = determineRequiredStatus(ann);
    currElements.add(new AutowiredFieldElement(field, required));
  }
});

public static void doWithLocalFields(Class<?> clazz, FieldCallback fc) {
  for (Field field : getDeclaredFields(clazz)) {
    fc.doWith(field);
  }
}

分析:
  根據上面的代碼能夠看到, Spring會遍歷一個個的屬性, 而後獲取到該屬性上@Autowired註解, 若是該註解
  不爲空, 則Spring會將其變成一個AutowiredFieldElement(繼承於InjectedElement), 而後將其添加到
  currentElements中, 在此之間, Spring還會判斷@Autowired註解中required屬性, 判斷是不是該屬性的
  注入是必須的, 若是一個屬性是static的, 那麼就直接返回了, 而且會打印一個info日誌(筆者沒寫出來)
複製代碼
  • 代碼二
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
  Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
  if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
    return;
  }
  AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
  if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
    if (Modifier.isStatic(method.getModifiers())) {
      return;
    }

    boolean required = determineRequiredStatus(ann);
    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
    currElements.add(new AutowiredMethodElement(method, required, pd));
  }
});

分析:
  在上面代碼中, 其實咱們能夠看到, 其實對@Autowired標註的方法和以前對屬性的處理是相似的, 都是對一個
  個的method進行循環, 而後一個個處理, 若是一個方法是static的則直接返回不進行處理了, 上面出現了一個
  橋接方法的定義, 筆者不太清楚這個橋接方法是什麼, 一般狀況下是直接返回咱們在類中定義的method的, 再
  日後, Spring調用了findPropertyForMethod獲取屬性裝飾器, 這裏簡單的擴展一下, PropertyDescriptor
  實際上是屬於Java層面的知識, 屬於Java內省機制的一部分, 其實就是Java中的一些定義而已, 在java中, 定
  義一個setXXX, getXXX, isXXX方法爲屬性的描述, Java中約定這些方法名中除了set、get、is以後的字母,
  則是Java中的屬性名稱(僅僅是一種約定而已, 咱們不必定要遵照), 用PropertyDescriptor類上的註釋描述
  爲: A PropertyDescriptor describes one property that a Java Bean exports via a
      pair of accessor methods.
  總之, findPropertyForMethod方法則是獲取該方法所在類中的全部get、set、is方法的屬性描述, 若是當前
  被遍歷的方法屬於其中一類, 則返回該方法的PropertyDescriptor, 若是不是, 好比checkXXX, 則返回null
複製代碼
  • 簡單的總結
在buildAutowiringMetadata方法中, Spring將一個類中@Autowired註解標註的方法和屬性變爲了一個個的
InjectedElement, 放入到一個elements的集合中, 最後將這個集合放入到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;
}

分析:
  能夠看到, Spring遍歷findAutowiringMetadata方法中找出來的一個個InjectedElement, 若是其知足代碼
  中的條件的話, 就將其放入到checkedElements中, 而這個條件以下:

public boolean isExternallyManagedConfigMember(Member configMember) {
  synchronized (this.postProcessingLock) {
    return (this.externallyManagedConfigMembers != null &&
        this.externallyManagedConfigMembers.contains(configMember));
  }
}

public void registerExternallyManagedConfigMember(Member configMember) {
  synchronized (this.postProcessingLock) {
    if (this.externallyManagedConfigMembers == null) {
      this.externallyManagedConfigMembers = new HashSet<>(1);
    }
    this.externallyManagedConfigMembers.add(configMember);
  }
}

分析:
  能夠看到, 當externallyManagedConfigMembers中不存在這個InjectElement的member(Method/Field)時,
  則調用registerExternallyManagedConfigMember方法放入進去, 若是出現兩個如出一轍的member, 則只會
  放入一個, 或許checkConfigMembers方法就是對InjectedElement的一種去重吧
複製代碼

總結

applyMergedBeanDefinitionPostProcessors方法經過調用一個個的MergedBeanDefinitionPostProcessor
中的postProcessMergedBeanDefinition方法完成了對被@Autowired等註解標註的方法、屬性的處理, 將其變
爲一個個的InjectionMetadata, 最終放入到該後置處理器中的injectionMetadataCache緩存中, 以後即可以
經過該後置處理器取得這些注入元數據, 進而完成屬性的注入, 這裏Spring也經過策略模式, 對不一樣類型的元數
據利用不一樣的後置處理器進行處理
複製代碼
相關文章
相關標籤/搜索