【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的整體邏輯。