【Spring Bean生命週期】聊透擴充套件點的流程及應用
theme: channing-cyan
開啟掘金成長之旅!這是我參與「掘金日新計劃 · 12 月更文挑戰」的第4天
Spring Bean生命週期,從入門到進階:
【Spring Bean生命週期】小白也能看懂的入門篇
【Spring Bean生命週期】高手如何解讀原始碼(一) - 資源的掃描和註冊
【Spring Bean生命週期】高手如何解讀原始碼(二) - Bean的生命週期
Spring之所以具備如此強大的生態,擴充套件能力強是很重要的一方面,這得益於它本身提供了非常多的擴充套件點。本節我們針對Spring生命週期中涉及到的擴充套件點來看一看,究竟它們是何方神聖?
1 擴充套件點流程
Spring生命週期的主要擴充套件點如圖:
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的整體邏輯。