【Spring Bean生命週期】聊透擴充套件點的流程及應用

語言: CN / TW / HK

theme: channing-cyan

開啟掘金成長之旅!這是我參與「掘金日新計劃 · 12 月更文挑戰」的第4天
Spring Bean生命週期,從入門到進階:
【Spring Bean生命週期】小白也能看懂的入門篇
【Spring Bean生命週期】高手如何解讀原始碼(一) - 資源的掃描和註冊
【Spring Bean生命週期】高手如何解讀原始碼(二) - Bean的生命週期

  Spring之所以具備如此強大的生態,擴充套件能力強是很重要的一方面,這得益於它本身提供了非常多的擴充套件點。本節我們針對Spring生命週期中涉及到的擴充套件點來看一看,究竟它們是何方神聖?

1 擴充套件點流程

  Spring生命週期的主要擴充套件點如圖:

image.png

2 擴充套件點解析及應用場景

ApplicationContextInitializer介面

  用於在執行refresh()之前初始化ConfigurationApplicationContext的回撥介面。
通常用於需要對應用程式上下文進行程式設計初始化的web應用程式中。例如,註冊屬性源或根據上下文環境啟用配置檔案。 比如:Spring boot就寫了幾個ApplicationContextInitializer的實現類,常見作用: - ConfigurationWarningsApplicationContextInitializer:對於一般配置錯誤在日誌中作出警告 - ContextIdApplicationContextInitializer:設定ApplicationContext#getId()所獲取的ID值,預設取spring.application.name屬性值,沒有配置時預設為 application - DelegatingApplicationContextInitializer:使用環境屬性context.initializer.classes指定的初始化器(initializers)進行初始化工作,如果沒有指定則什麼都不做
......
實現方式如下 public class CustomApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { // 列印當前方法類名和方法名,除錯用 CountUtil.println(getClass(),Thread.currentThread().getStackTrace()[2].getMethodName()); } } 在配置檔案中設定: context.initializer.classes=com.zyq.demo.expand.CustomApplicationContextInitializer

BeanDefinitionRegistryPostProcessor

  這個介面在讀取專案中的beanDefinition之後執行,提供一個補充的擴充套件點。使用場景:可以在這裡動態註冊自己的beanDefinition,可以載入classpath之外的bean。

  比如我們在經常使用的mybatis的mapper和我們的feignClient,往往只需要定義介面而不需要寫實現,那麼這些實現的生成及注入就是在此處完成的。 ``` @Component public class CustomBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // 可以在此處自定義掃描mapper,並生成BeanDefinition資訊,通過registry註冊到容器中 RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Test001.class); registry.registerBeanDefinition("test001",rootBeanDefinition);

    CountUtil.println(getClass(),Thread.currentThread().getStackTrace()[2].getMethodName());
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    CountUtil.println(getClass(),Thread.currentThread().getStackTrace()[2].getMethodName());
}

} ```

BeanFactoryPostProcessor

  這個介面是beanFactory的擴充套件介面,呼叫時機在spring在讀取beanDefinition資訊之後,例項化bean之前。在這個時機,使用者可以通過實現這個擴充套件介面來自行處理一些東西,比如修改已經註冊的beanDefinition的元資訊。根據註釋可知,此時只能承上(即修改元資訊),堅決不允許啟下(即觸發例項化)

  實際工作中,自定義BeanFactoryPostProcessor的情況確實少,比如對敏感資訊的解密處理,在資料庫的連線配置中,使用者名稱和密碼都是明文配置的,這就存在洩漏風險,還有redis的連線配置、shiro的加密演算法、rabbitmq的連線配置等等,凡是涉及到敏感資訊的,都需要進行加密處理,資訊保安非常重要。

配置的時候以密文配置,在真正用到之前在spring容器中進行解密,然後用解密後的資訊進行真正的操作。 @Component public class CustomBeanFactoryAware implements BeanFactoryPostProcessor { public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { MutablePropertySources propSources = environment.getPropertySources(); StreamSupport.stream(propSources.spliterator(), false) .filter(ps -> ps instanceof OriginTrackedMapPropertySource) .collect(Collectors.toList()) .forEach(ps -> convertPropertySource((PropertySource<LinkedHashMap>) ps)); LOGGER.info("敏感資訊解密完成....."); } }

InstantiationAwareBeanPostProcessor

  InstantiationAwareBeanPostProcessor介面繼承BeanPostProcessor介面,它內部提供了3個方法,再加上BeanPostProcessor介面內部的2個方法,所以實現這個介面需要實現5個方法。可以稱作是Spring生命週期的大寶劍!Spring的核心功能之一AOP就是基於它來實現的。 @Component public class CustomInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { //在例項化之前被呼叫 @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { if(beanClass == TargetClass.class){ //使用cglib實現動態代理AOP Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(beanClass); enhancer.setCallback(new TargetClassProxy()); TargetClass proxyClass = (TargetClass)enhancer.create(); return proxyClass; } return null; } }

BeanNameAware

  這個類也是Aware擴充套件的一種,觸發點在bean的初始化之前,也就是postProcessBeforeInitialization之前,這個類的觸發點方法只有一個:setBeanName。使用場景:使用者可以擴充套件這個點,在初始化bean之前拿到spring容器中註冊的的beanName,來自行修改這個beanName的值。整個約定了在編寫服務程式碼時,就可以知道客戶端呼叫時的bean名稱,可在此處打標籤,用於一些特殊bean的捕獲。 ``` public class CustomBeanNameAware implements BeanNameAware { @Override public void setBeanName(String name) {

    System.out.println("在這裡修改Bean名稱");
}

} ```

@PostConstruct

  @PostConstruct這個並不算一個擴充套件點,其實就是一個標註。從Java EE5規範開始,Servlet中增加的註解,被@PostConstruct修飾的方法會在伺服器載入Servlet的時候執行,並且只會被伺服器執行一次。其作用是在bean的初始化階段,如果對一個方法標註了@PostConstruct,會先呼叫這個方法。這裡重點是要關注下這個標準的觸發點,這個觸發點是在postProcessBeforeInitialization之後,InitializingBean.afterPropertiesSet之前。

  使用場景:如果想在生成物件時候完成某些初始化操作,而偏偏這些初始化操作又依賴於依賴注入,那麼就無法在建構函式中實現。為此,可以使用@PostConstruct註解一個方法來完成初始化,@PostConstruct註解的方法將會在依賴注入完成後被自動呼叫。

  spring自帶的@schedule,沒有開關,專案啟動總會啟動一個執行緒;做專案的時候就使用Java的timer,這個設定開關即可自由的控制,關閉的時候,不會啟動執行緒;Java的timer也需要找到一個啟動類,可以放到main函式裡面啟動,這樣的話,程式碼的耦合性太高了,而使用PostConstruct是很乾淨的。

``` @PostConstruct public void init() { // 啟動定時任務執行緒 }

```

InitializingBean

  InitializingBean的作用是Bean注入到Spring容器且初始化後,執行特定業務化的操作。Spring允許容器中的Bean,在Bean初始化完成後或者Bean銷燬前,執行特定業務化的操作,常用的實現方式有以下三種: - 通過實現InitializingBean/DisposableBean介面來處理初始化後/銷燬前的操作; - 通過標籤的init-method/destroy-method屬性處理初始化後/銷燬前的操作; - 在指定方法上加上@PostConstruct或@PreDestroy註解來處理初始化後/銷燬前的操作。

在spring初始化bean的時候,如果該bean是實現了InitializingBean介面,並且同時在配置檔案中指定了init-method,系統則是先呼叫afterPropertiesSet方法,然後在呼叫init-method中指定的方法

``` public class CustomInitializingBean implements InitializingBean {

@Override
public void afterPropertiesSet() throws Exception {

    if (StringUtils.isEmpty(path)) {
        log.error("PLEASE SET THE RULE'S PATH: (spring.drools.path = XXX).");
    }
}

} ```

BeanPostProcessor

  在Bean物件在例項化和依賴注入完畢後,在顯示呼叫初始化方法的前後新增我們自己的邏輯。注意是Bean例項化完畢後及依賴注入完成後觸發的。
``` public interface BeanPostProcessor {

/* * Apply this {@code BeanPostProcessor} to the given new bean instance before any bean * initialization callbacks (like InitializingBean's {@code afterPropertiesSet} * or a custom init-method). The bean will already be populated with property values. * The returned bean instance may be a wrapper around the original. / @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; }

/* * Apply this {@code BeanPostProcessor} to the given new bean instance after any bean * initialization callbacks (like InitializingBean's {@code afterPropertiesSet} * or a custom init-method). The bean will already be populated with property values. * The returned bean instance may be a wrapper around the original. / @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }

} ``` 從程式碼可以看出來,返回的時經過處理後的Bean,這種就很適合代理類的場景,如果需要實現類似AOP的邏輯,可以在此處理。

好了,上面就是涉及到Spring生命週期的一些主要擴充套件點,主要是幫助我們集合Spring生命週期來理解Spring的整體邏輯。