@PostConstruct注解原理解析


所有文章

https://www.cnblogs.com/lay2017/p/11478237.html

 

正文

@PostConstruct注解使用簡介

在了解一個東西的原理之前,我們得初步的懂得如何使用它。所以,本文先從@PostConstruct注解如何簡單的使用開始。

簡單起見,我們准備一個springboot項目快速啟動。項目目錄結構如下:

 

下面我們在cn.lay.postconstruct目錄下創建一個類,並添加一個@PostConstruct的方法,如

 

最后,我們執行PostConstructApplication的main方法,啟動項目。在控制台里,我們會看到

到這里,我們可以知道@PostConstruct注解的用途了。當一個class被注解為一個Bean,那么class上被@PostConstruct注解的方法將會在程序啟動的時候執行。

知道了如何使用@PostConstruct以后,我們會產生疑問。為什么@PostConstruct注解的方法會在程序啟動的時候執行呢?后續的內容將為你解開疑惑。

 

回顧spring中一個Bean的創建過程

在關注@PostConstruct原理之前,我們不得不先回顧一下spring中一個Bean是如何被創建的,這將有助於我們理清脈絡。

配置Bean通常采用xml配置或者@Component、@Service、@Controller等注解配置,這是我們很熟悉的。這意味着Bean的創建過程第一步是配置Bean

配置Bean --> 

無論是xml配置,還是注解配置,都會執行解析處理,處理后的結果會變成BeanDefinition這樣的對象,存儲在Bean容器里面。我們可以把BeanDefinition理解為Bean的元數據。

所以,第二步就是將配置解析成Bean的元數據

配置Bean --> 解析為Bean的元數據 --> 

到這里,還只是Bean元數據,並不是我們最熟悉的Bean。所以,第三步就會根據Bean的元數據來創建Bean了。

這里注意了,觸發某個Bean的創建,就是從Bean容器中第一次獲取Bean的時候,也就是BeanFactory的getBean()方法。而不是解析了Bean元數據后就馬上創建為Bean。

配置Bean --> 解析為Bean的元數據 --> 根據Bean的元數據生成Bean

這樣,我們就大體明白了一個Bean的創建過程。生成的Bean將會存放在Bean容器當中,或者我們稱呼其為Bean工廠。

 

@PostConstruct原理

前面,我們了解了Bean的創建過程。而@PostConstruct方法將在最后生成Bean的時候被調用。getBean方法是一個Bean生成的入口,為此,我們找到BeanFactory的getBean方法。

BeanFactory是一個抽象接口,它抽象了一個Bean工廠或者Bean容器。BeanDefinition和Bean實例都存放在BeanFactory中。

 

那么,我們根據繼承關系,向下找到AbstractAutowireCapableBeanFactory這個抽象類,該類間接實現了BeanFactory,並跟進其中一個getBean方法

 

再跟進doGetBean,doGetBean方法很長,我們做一些刪減。關注一下核心內容

protected <T> T doGetBean(
        final String name, 
        @Nullable final Class<T> requiredType,
        @Nullable final Object[] args, 
        boolean typeCheckOnly
        ) throws BeansException {

    final String beanName = transformedBeanName(name);

    Object bean;

    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        // ...
    } else {
        try {
            // ...

            // 創建Bean實例
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        return createBean(beanName, mbd, args);
                    } catch (BeansException ex) {
                        // ...
                    }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            } else if (mbd.isPrototype()) {
                // ...
            } else {
                // ...
            }
        } catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
    }
    // ...

    return (T) bean;
}

這里以創建單例Bean為例,我們注意到createBean方法將會創建一個Bean實例,所以createBean方法包含了創建一個Bean的核心邏輯。

 

再跟進createBean方法

protected Object createBean(
        String beanName, 
        RootBeanDefinition mbd, 
        @Nullable Object[] args)
        throws BeanCreationException {

    // ...

    try {
        Object beanInstance = doCreateBean(beanName, mbdToUse, args); // ...
        return beanInstance;
    } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
        // ...
    } catch (Throwable ex) {
        // ...
    }
}

 

createBean委托給了doCreateBean處理

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException {

    BeanWrapper instanceWrapper = null;
    // ...

    // 創建Bean的實例對象
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    
    // ...

    // 初始化一個Bean
    Object exposedObject = bean;
    try {
        // 處理Bean的注入
 populateBean(beanName, mbd, instanceWrapper); // 處理Bean的初始化操作
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable ex) {
        // ...
    }

    // ...

    return exposedObject;
}

到這里,我們可以知道。BeanFactory的getBean()方法將會去創建Bean,在doCreateBean方法的創建邏輯中主要包含了三個核心邏輯:

1)創建一個Bean的實例對象,createBeanInstance方法執行

2)處理Bean之間的依賴注入,比如@Autowired注解注入的Bean。所以,populateBean方法將會先去處理注入的Bean,因此對於相互注入的Bean來說不用擔心Bean的生成先后順序問題。

3)Bean實例生成,相互注入以后。還需要對Bean進行一些初始化操作。比如我們@PostConstruct注解注釋的方法,將再初始化的時候被解析並調用。當然還有一些Aware接口,@Schedule注解啥的也會做相應的處理。

 

我們繼續跟進初始化過程,進入initializeBean方法

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    // ...

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 初始化前置處理
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // 調用初始化方法
 invokeInitMethods(beanName, wrappedBean, mbd);
    } catch (Throwable ex) {
        // ...
    }
    if (mbd == null || !mbd.isSynthetic()) {
        // 初始化后置處理
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}

這里主要包含了初始化的前置、后置處理,以后初始化方法的調用。@PostConstruct注解將在applyBeanPostProcessorsBeforeInitialization這個前置處理

 

我們跟進applyBeanPostProcessorsBeforeInitialization前置方法

@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
        throws BeansException {

    Object result = existingBean;
    // 遍歷所有的后置處理器
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        // 調用初始化前置方法
        Object current = processor.postProcessBeforeInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

我們注意到,這里遍歷了在spring啟動過程中被注冊的BeanPostProcessor接口,並調用其前置方法。

BeanPostProcessor接口被稱作Bean的后置處理器接口,也就是說當一個Bean生成以后,會針對生成的Bean做一些處理。比如我們注解了@PostConstruct注解的Bean將會被其中一個BeanPostProcessor處理。或者一些@Schedule之類注解的Bean也會被處理,等一些所謂的后置處理操作。

 

到這里呢,我們意識到,原來@PostConstruct注解是會被一個專門的BeanPostProcessor接口的具體實現類來處理的。

我們找到該實現類:InitDestroyAnnotationBeanPostProcessor,根據名字我們就大體可以知道這個后置處理器是用於處理Bean的初始化方法注解和Bean的銷毀方法注解的。這里,我們只關注@PostConstruct初始化注解相關的

 

我們跟進InitDestroyAnnotationBeanPostProcessor的postProcessBeanInitialization方法

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    // 元數據解析
    LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
    try {
        // 觸發初始化方法
        metadata.invokeInitMethods(bean, beanName);
    }
    catch (InvocationTargetException ex) {
        throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
    }
    return bean;
}

findLifecycleMetadata方法將會解析元數據,所以@PostConstruct注解的初始化方法也會在這里被找到。

invokeInitMethods方法將會觸發上一步被找到的方法。

其實,到這里我們大體都可以猜測這兩個方法的邏輯了。就是通過反射將Method給找出來,然后再通過反射去調用這些method方法。

 

跟進findLifecycleMetadata方法看看初始化方法的查找過程吧

private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
    // ...
    
    LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);
    if (metadata == null) {
        synchronized (this.lifecycleMetadataCache) {
            metadata = this.lifecycleMetadataCache.get(clazz);
            if (metadata == null) {
                // 構建元數據
                metadata = buildLifecycleMetadata(clazz);
                this.lifecycleMetadataCache.put(clazz, metadata);
            }
            return metadata;
        }
    }
    return metadata;
}

這里做了一個雙重校驗來控制緩存,我們更關心的是buildLifecycleMetadata這個構建方法

 

跟進方法,簡單起見這里刪除了destroy相關的部分

private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
    List<LifecycleElement> initMethods = new ArrayList<>();
    Class<?> targetClass = clazz;

    do {
        final List<LifecycleElement> currInitMethods = new ArrayList<>();// 變量類中的方法Method對象
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 判斷是否被@PostConstruct注解注釋
            if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
                LifecycleElement element = new LifecycleElement(method);
                currInitMethods.add(element);
            }
            // ...
        });

        // 添加到集合中,后續調用
        initMethods.addAll(0, currInitMethods); // ...
        targetClass = targetClass.getSuperclass();
    } while (targetClass != null && targetClass != Object.class);

    return new LifecycleMetadata(clazz, initMethods, destroyMethods);
}

可以看到,doWithLocalMethods這個工具方法將會從class中獲取方法的反射對象。而后判斷該方法是否被被initAnnotationType指定的注釋注解。最后,添加到initMethods集合當中供后續反射調用。

這里還向父類進行了遞歸處理,直到Object類為止。

我們看看initAnnotationType是一個什么注解

public CommonAnnotationBeanPostProcessor() {
    setOrder(Ordered.LOWEST_PRECEDENCE - 3);
    setInitAnnotationType(PostConstruct.class);
    setDestroyAnnotationType(PreDestroy.class);
    ignoreResourceType("javax.xml.ws.WebServiceContext");
}

可以看到,@PostConstruct注解被設置為了initAnnotationType的值。值得注意的是,這是在CommonAnnotationBeanPostProcessor這個后置處理器的構造方法中執行的。

而CommonAnnotationBeanPostProcessor和InitDestroyAnnotationBeanPostProcessor的關系是繼承關系,前者繼承了后者。我們可以斷點看看getBeanPostProcessors方法

 

到這里,我們可以知道。跟我們前面的猜測一樣,解析過程是通過反射來獲取@PostConstruct注解的方法,並放到一個List集合里面去。

下面,我們再簡單看看這些Method被調用的過程吧。

回到InitDestroyAnnotationBeanPostProcessor的postProcessBeforeInitialization方法

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
    try {
        metadata.invokeInitMethods(bean, beanName);
    }
    catch (InvocationTargetException ex) {
        throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
    }
    return bean;
}

這次我們關注invokeInitMethods方法

public void invokeInitMethods(Object target, String beanName) throws Throwable {
    Collection<LifecycleElement> checkedInitMethods = this.checkedInitMethods;
    Collection<LifecycleElement> initMethodsToIterate = (checkedInitMethods != null ? checkedInitMethods : this.initMethods);
    if (!initMethodsToIterate.isEmpty()) {
        for (LifecycleElement element : initMethodsToIterate) {
            if (logger.isTraceEnabled()) {
                logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod());
            }
            // 調用
            element.invoke(target);
        }
    }
}

跟進invoke,單純地invoke了method,是我們很熟悉的反射調用

public void invoke(Object target) throws Throwable {
    ReflectionUtils.makeAccessible(this.method);
    this.method.invoke(target, (Object[]) null);
}

 

總結

至此,本文就結束了。做一個簡單的總結,本文內容包含三塊:1)如何使用@PostConstruct;2)Bean創建過程簡介;3)@PostConstruct的原理分析。

我們提出了一個問題:為什么@PostConstruct注解的方法會在啟動的時候執行呢?

到這里大家應該能夠知道答案了,spring的Bean在創建的時候會進行初始化,而初始化過程會解析出@PostConstruct注解的方法,並反射調用該方法。從而,在啟動的時候該方法被執行了。

還有一個小點要注意,spring中的Bean默認是不會lazy-init的,所以在啟動過程就會調用getBean方法。如果不希望該Bean在啟動過程就調用,那么將lazy-init設置為true,它就會在程序第一次使用的時候進行初始化。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM