http://www.javashuo.com/article/p-uvudtich-bm.htmlhtml
在瞭解一個東西的原理以前,咱們得初步的懂得如何使用它。因此,本文先從@PostConstruct註解如何簡單的使用開始。java
簡單起見,咱們準備一個springboot項目快速啓動。項目目錄結構以下:spring
下面咱們在cn.lay.postconstruct目錄下建立一個類,並添加一個@PostConstruct的方法,如緩存
最後,咱們執行PostConstructApplication的main方法,啓動項目。在控制檯裏,咱們會看到springboot
到這裏,咱們能夠知道@PostConstruct註解的用途了。當一個class被註解爲一個Bean,那麼class上被@PostConstruct註解的方法將會在程序啓動的時候執行。app
知道了如何使用@PostConstruct之後,咱們會產生疑問。爲何@PostConstruct註解的方法會在程序啓動的時候執行呢?後續的內容將爲你解開疑惑。ide
在關注@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工廠。
前面,咱們瞭解了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,它就會在程序第一次使用的時候進行初始化。