所有文章
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,它就會在程序第一次使用的時候進行初始化。