探索Spring Boot中@PostConstruct的魔法
前言
@postContruct全限定類名是javax.annotation.PostConstruct,可以看出來其本身不是Spring定義的注解,但是Spring提供了具體的實現(xiàn),所以這篇文章主要分析的是@PostConstruct在Spring項目開發(fā)中的功能特性、實現(xiàn)方式和基本工作原理。
功能特性
從@PostConstruct注解的注釋上看,可以了解到以下內(nèi)容:
1、要在依賴加載后,對象佤用前執(zhí)行,并且只執(zhí)行一次;
2、所有支持依賴注入的類都需要支持此方法。即使類沒有請求注入任何的資源,也必須調(diào)用被@PostConstruct注解標(biāo)記的方法;
3、一個類中在一個方法上使用@PostConstruct注解;
4、使用@PostConstruct注解標(biāo)記的方法不能有參數(shù),除非是攔截器,可以采用攔截器規(guī)范定義的InvocationContext對象。
5、使用@PostConstruct注解標(biāo)記的方法不能有返回值,實際上如果有返回值,也不會報錯,但是會忽略掉;
6、使用@PostConstruct注解標(biāo)記的方法的權(quán)限,public、private、protected都可以;
7、使用@PostConstruct注解標(biāo)記的方法不能被static修飾,但是final是可以的;
package javax.annotation;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}
但是在在實際的Spring項目中Bean的生命周期里,其執(zhí)行的時機(jī)是:1、Bean的實例化;2、Bean內(nèi)依賴屬性的注入 ;3、Bean里被@PostConstruct標(biāo)記的方法;
下面在實現(xiàn)方式里,用一個小例子來驗證一下這個過程;
實現(xiàn)方式
1、定義一個ExampleController類,采用setter的依賴注入的方式,注入exampleService屬性,另外在定義一個myPostConstruct方法用@PostConstruct注解標(biāo)記;
@RestController
@Slf4j
public class ExampleController {
private ExampleService exampleService;
public ExampleController() {
log.info("----ExampleController無參數(shù)構(gòu)造方法被執(zhí)行");
}
@Autowired
public void setExampleService(ExampleService exampleService) {
this.exampleService = exampleService;
log.info("----ExampleController類的setExampleService方法被調(diào)用");
}
@PostConstruct
public void myPostConstruct(){
log.info("----ExampleController類的myPostConstruct方法被調(diào)用");
}
}
2、定義ExampleService類
@Service
@Slf4j
public class ExampleService {
public ExampleService() {
log.info("----ExampleService的無參數(shù)構(gòu)造方法被調(diào)用");
}
}
3、定義一個單元測試,在單元測試中啟動Spring容器;
@Test
public void test4(){
log.info("----單元測試執(zhí)行開始");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
log.info("----單元測試執(zhí)行完畢");
}
單元測試驗證結(jié)果:
圖片
從單元測試的執(zhí)行結(jié)果來看,首先,ExampleConstroller被實例化,接著是ExampleService被實例化,然后通過setter依賴注入的方式把ExampleService對象注入到了ExampleConstroller對象中,之后才開始了被@PostConstruct注解標(biāo)記的myPostConstruct方法的執(zhí)行。下面就單元測試的結(jié)果分析一個@PostConstruct注解的工作原理。
工作原理
@PostConstruct的工作原理的關(guān)鍵問題就是:在Spring容器啟動的過程,被@PostConstruct標(biāo)記的方法是怎么被執(zhí)行的?
在被@PostConstruct標(biāo)記的方法上打上斷點,待程序執(zhí)行的斷點的時候觀察一下方法調(diào)用棧信息,這時會發(fā)現(xiàn):
1、Spring容器啟動過程的最后一步,即把需要提前注冊的一些非懶加載的單例Bean時,如ExampleController,注意這時exampleController對象實例化完成,需要注入的exampleService的屬性已經(jīng)被實例化,且已經(jīng)注入到exampleController對象中,在BeanPostProcessor接口的擴(kuò)展方法中,被@PostConstruct標(biāo)記的方法開始觸發(fā)執(zhí)行,入口位置在AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization。
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;
}
圖片
那么觸發(fā)被@PostConstruct注解標(biāo)記的方法執(zhí)行的BeanPostProcessor接口的具體是實現(xiàn)是哪個類呢?通過debug分析,是CommonAnnotationBeanPostProcessor類。
圖片
2、CommonAnnotationBeanPostProcessor類繼承于InitDestroyAnnotationBeanPostProcessor,實際的觸發(fā)@PostConstruct標(biāo)記方法執(zhí)行的入口是在InitDestroyAnnotationBeanPostProcessor的postProcessBeforeInitialization()
3、InitDestroyAnnotationBeanPostProcessor的postProcessBeforeInitialization()內(nèi),邏輯相對比較簡潔,先查詢bean中被@PostConstruct標(biāo)記的方法,然后再使用java反射來執(zhí)行這個方法;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//查詢bean中被@PostConstruct標(biāo)記的方法,相關(guān)的信息封在LifecycleMetadata對象的
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
//使用java反射執(zhí)行被@PostConstruct標(biāo)記的方法
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;
}
圖片
總結(jié)
從以上幾步的分析來看,被@PostConstruct標(biāo)記的方法是怎么被執(zhí)行的,這個問題回答清楚了。如果面試官問你,你了解@PostContruct注解是怎么工作的嗎?你就可以這么回答他:在Bean實例化、屬性注入后,被@PostConstruct標(biāo)記的方法是在BeanPostProcessor的擴(kuò)展方法postProcessBeforeInitialization()觸發(fā)執(zhí)行的,具體實現(xiàn)類是InitDestroyAnnotationBeanPostProcessor,具體的邏輯是:先查詢被@PostConstruct標(biāo)記的方法,然后使用java反射去執(zhí)行這個方法。回答完后,如果他不換一個問題的話,把Springboot的擴(kuò)展點都給他盤一遍。