Spring IoC 公共註解詳解

leisurexi發表於2020-07-08

前言

本系列全部基於 Spring 5.2.2.BUILD-SNAPSHOT 版本。因為 Spring 整個體系太過於龐大,所以只會進行關鍵部分的原始碼解析。

什麼是公共註解?公共註解就是常見的Java註解,特別是JSR-250中的註解。例如:@Resource@PostConstructor@PreDestroy 等等,本文也就主要分析這三個註解在 Spring 中是如何處理的。

正文

@Resource 註解的處理

@Resource 註解的處理類是 CommonAnnotationBeanPostProcessor,它通過實現 InstantiationAwareBeanPostProcessor 介面,重寫 postProcessProperties() 方法實現對標註了 @Resource 註解的欄位或方法的自動注入

InstantiationAwareBeanPostProcessor 介面的詳細資訊可以檢視Spring IoC bean 的建立

關於 CommonAnnotationBeanPostProcessor 這個後置處理器是怎麼加入到 beanFactory 中的,我們在 Spring IoC component-scan 節點詳解 一文中介紹過主要是通過 AnnotationConfigUtils#registerAnnotationConfigProcessors() 實現的。

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
    
    // 省略其他程式碼...
    // 註冊用於處理@Resource、@PostConstructor、@PostDestroy註解的後置處理器
    if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
    
    // 省略其他程式碼...
    
}

BeanDefinition 合併後的後置處理

CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
    // 尋找需要注入的欄位或方法,並封裝成 InjectionMetadata
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 檢查後設資料中的註解資訊
    metadata.checkConfigMembers(beanDefinition);
}

上面程式碼中的 findAutowiringMetadata() 方法就是利用反射遍歷類的所有欄位和方法,找到標註了 @Resource 註解的,並快取進 injectionMetadataCache 中。

注意:靜態欄位和靜態方法會過濾掉。

findAutowiringMetadata() 方法基本和 AutowiredAnnotationBeanPostProcessor 中的一致,只是處理的註解不同而已,可以查檢視Spring IoC @Autowired 註解詳解一文中該方法的詳解。

bean 屬性後置處理

CommonAnnotationBeanPostProcessor#postProcessProperties

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 從injectionMetadataCache快取中獲取需要注入的欄位和方法
    InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
    try {
        // 進行注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
    }
    return pvs;
}

// InjectMetadata.java
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 獲取檢查後的元素
    Collection<InjectedElement> checkedElements = this.checkedElements;
    // 如果checkedElements不為空就使用checkedElements,否則使用injectedElements
    Collection<InjectedElement> elementsToIterate =
        (checkedElements != null ? checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
        // 遍歷elementsToIterate
        for (InjectedElement element : elementsToIterate) {
            if (logger.isTraceEnabled()) {
                logger.trace("Processing injected element of bean '" + beanName + "': " + element);
            }
            // 進行元素注入,見下文詳解
            element.inject(target, beanName, pvs);
        }
    }
}

元素注入

InjectionMetadata#inject
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
    throws Throwable {
    // 如果元素是欄位
    if (this.isField) {
        // 強轉成Field型別
        Field field = (Field) this.member;
        // 並設定為可訪問
        ReflectionUtils.makeAccessible(field);
        // 然後使用反射設定值
        field.set(target, getResourceToInject(target, requestingBeanName));
    }
    else {
        // 檢查是否跳過
        if (checkPropertySkipping(pvs)) {
            return;
        }
        try {
            // 強轉成Method型別
            Method method = (Method) this.member;
            // 並設定為可訪問
            ReflectionUtils.makeAccessible(method);
            // 使用反射呼叫方法
            method.invoke(target, getResourceToInject(target, requestingBeanName));
        }
        catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

獲取注入資源

ResourceElement#getResourceToInject
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
    return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : getResource(this, requestingBeanName));
}

上面的 lazyLookup 就是是否在屬性或方法上標註了 @Lazy 註解,該註解先暫不討論,所以呼叫後面的 getResource() 方法。

CommonAnnotationBeanPostProcessor#getResource
protected Object getResource(LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException {
	// 省略其它程式碼...
    return autowireResource(this.resourceFactory, element, requestingBeanName);
}

protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException {

    Object resource;
    Set<String> autowiredBeanNames;
    String name = element.name;

    if (factory instanceof AutowireCapableBeanFactory) {
        AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
        DependencyDescriptor descriptor = element.getDependencyDescriptor();
        // 型別匹配(預設為true) && @Resource註解name屬性不為空 && 當前beanFactory不包含名稱為name的bean
        if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
            autowiredBeanNames = new LinkedHashSet<>();
            // 按型別查詢bean
            resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
            if (resource == null) {
                throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
            }
        }
        else {
            // 按名稱查詢bean
            resource = beanFactory.resolveBeanByName(name, descriptor);
            autowiredBeanNames = Collections.singleton(name);
        }
    }
    // 省略其它程式碼...

    return resource;
}

上面 autowireResource() 方法中按型別查詢的 resolveDependency() 方法在Spring IoC bean 的建立一文中分析過,按名稱查詢 beanresolveBeanByName() 方法實際就是呼叫 getBean() 通過名稱和型別去獲取 bean

從上面的程式碼也可以看出一般情況下 @Resource 註解是按名稱注入;而 @Autowired 註解時按型別注入,具體可以檢視Spring IoC @Autowired 註解詳解

@PostConstruct、@PreDestroy 註解的處理

處理 @PostConstruct@PreDestroy 註解的處理類是 InitDestroyAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 繼承與該類,相當於註冊 CommonAnnotationBeanPostProcessor 時也註冊了 InitDestroyAnnotationBeanPostProcessor

BeanDefinition 合併後的後置處理

InitDestroyAnnotationBeanPostProcessor#postProcessMergedBeanDefinition() 方法是通過 CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition() 方法呼叫的,如下:

CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 呼叫InitDestroyAnnotationBeanPostProcessor的postProcessMergedBeanDefinition()方法
    super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
    // 尋找需要注入的欄位或方法,並封裝成 InjectionMetadata
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 檢查後設資料中的註解資訊
    metadata.checkConfigMembers(beanDefinition);
}

InitDestroyAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 尋找需要標註了@PostConstruct和@PreDestroy註解的方法,並封裝進LifecycleMetadata
    LifecycleMetadata metadata = findLifecycleMetadata(beanType);
    // 檢查後設資料中的註解資訊
    metadata.checkConfigMembers(beanDefinition);
}

上面程式碼中的 findLifecycleMetadata() 方法,就是遍歷當前初始化的 bean 包括其父類中所有標註了 @PostConstruct@PreDestroy 註解的方法,並封裝成 LifecycleMetadata (該類是 InitDestroyAnnotationBeanPostProcessor 中一個內部類),並放入 lifecycleMetadataCache 快取中。

這裡我們簡單看一下 LifecycleMetadata 這個類:

private class LifecycleMetadata {

    // 目標類,也就是當前正在初始化的bean
    private final Class<?> targetClass;
	// 存放標註了@PostConstruct的方法
    private final Collection<LifecycleElement> initMethods;
	// 存放標註了@PreDestroy的方法
    private final Collection<LifecycleElement> destroyMethods;

	// 省略其它程式碼...
}

bean 的初始化前回撥

InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    // 從lifecycleMetadataCache快取中獲取LifecycleMetadata
    LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
    try {
        // 反射呼叫所有初始化方法
        metadata.invokeInitMethods(bean, beanName);
    }
	// 省略異常處理...
    return bean;
}

看到這裡我們知道為什麼標註了 @PostConstruct 註解的方法比 InitializingBean#afterPropertiesSet() 方法和自定義初始化方法先呼叫了;因為其在 bean 的初始化前回撥就已經呼叫了,而剩下的兩個是在初始化方法中呼叫的,詳情可以檢視Spring IoC bean 的初始化一文。

bean 銷燬前回撥

我們先了解一下 DestructionAwareBeanPostProcessor,它繼承自 BeanPostProcessor,如下:

public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {

    /**
     * Bean 銷燬前階段回撥
     */
    void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;

    /**
     * bean例項是否要由此方法銷燬
     */
    default boolean requiresDestruction(Object bean) {
        return true;
    }

}

InitDestroyAnnotationBeanPostProcessor#postProcessBeforeDestruction

public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
    // 從lifecycleMetadataCache快取中獲取LifecycleMetadata
    LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
    try {
        // 反射呼叫所有銷燬方法
        metadata.invokeDestroyMethods(bean, beanName);
    }
	// 省略異常處理...
}

和上面的 @PostConstruct 註解一樣,@PreDestroy 註解標註的方法也比 DisposableBean#destroy() 方法和自定義銷燬方法先呼叫。

總結

本文主要介紹了 @Resource@PostConstruct@PreDestroy 註解 Spring 是如何對其處理的,可以看出 Spring 的註解驅動大多依靠 實現 BeanPostProcessor 及其子類中的 bean 生命週期各個階段的回撥方法來進行實現的。

最後,我模仿 Spring 寫了一個精簡版,程式碼會持續更新。地址:https://github.com/leisurexi/tiny-spring

相關文章