@PostConstruct註解原理解析

全部文章

http://www.javashuo.com/article/p-uvudtich-bm.htmlhtml

 

正文

@PostConstruct註解使用簡介

在瞭解一個東西的原理以前,咱們得初步的懂得如何使用它。因此,本文先從@PostConstruct註解如何簡單的使用開始。java

簡單起見,咱們準備一個springboot項目快速啓動。項目目錄結構以下:spring

 

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

 

最後,咱們執行PostConstructApplication的main方法,啓動項目。在控制檯裏,咱們會看到springboot

到這裏,咱們能夠知道@PostConstruct註解的用途了。當一個class被註解爲一個Bean,那麼class上被@PostConstruct註解的方法將會在程序啓動的時候執行。app

知道了如何使用@PostConstruct之後,咱們會產生疑問。爲何@PostConstruct註解的方法會在程序啓動的時候執行呢?後續的內容將爲你解開疑惑。ide

 

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

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

配置Bean一般採用xml配置或者@Component、@Service、@Controller等註解配置,這是咱們很熟悉的。這意味着Bean的建立過程第一步是配置Beanpost

配置Bean --> 

不管是xml配置,仍是註解配置,都會執行解析處理,處理後的結果會變成BeanDefinition這樣的對象,存儲在Bean容器裏面。咱們能夠把BeanDefinition理解爲Bean的元數據。ui

因此,第二步就是將配置解析成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,它就會在程序第一次使用的時候進行初始化。

相關文章
相關標籤/搜索