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