強大!牢記這16個Spring Boot擴展接口,代碼優(yōu)雅提升一個層次
Spring的核心理念是它的容器。當容器刷新時,表面看似風平浪靜,內(nèi)部卻如同風起云涌的大海,廣闊而洶涌。SpringBoot更進一步,將Spring封裝起來,遵循“約定優(yōu)于配置”的原則,并結(jié)合自動配置機制。通常情況下,只需添加一個依賴,我們就能以最小配置甚至零配置實現(xiàn)功能。
我尤其喜歡自動配置機制,因此在開發(fā)中間件和通用依賴工具時經(jīng)常使用這個功能。這種方法允許用戶以最低的成本進行集成。要掌握自動配置,必須了解Spring Bean的構(gòu)建生命周期以及各種擴展接口。當然,理解Bean的不同生命周期也能幫助更深入地理解Spring,業(yè)務(wù)代碼也可以合理地利用這些擴展點編寫更加優(yōu)雅的代碼。
在本文中,我總結(jié)了幾乎所有Spring和SpringBoot的擴展接口及其應(yīng)用場景。同時,我整理了Bean從加載到最終初始化過程中所有可擴展點的時序圖,這使我們能夠一窺Bean是如何逐步加載到Spring容器中的。
文章內(nèi)容較長,請耐心閱讀!
啟動期間可擴展接口調(diào)用的時序圖
以下是Bean在Spring容器中的生命周期中所有可擴展點的順序圖。
接下來我將逐一分析每一個。
圖片
ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer
這是一個用于在整個Spring容器刷新之前初始化ConfigurableApplicationContext的回調(diào)接口。簡單來說,在容器刷新之前,會調(diào)用該類的initialize方法,此時允許用戶擴展。用戶可以在整個Spring容器初始化之前做一些事情。
可能的使用場景包括在最初激活某些配置,或利用類加載器加載類之前的時機執(zhí)行如動態(tài)字節(jié)碼注入等操作。
擴展方法如下:
public class TestApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("[ApplicationContextInitializer]");
}
}
由于此時Spring容器尚未初始化,因此有三種方式使你的擴展生效:
- 在啟動類中添加springApplication.addInitializers(new TestApplicationContextInitializer())。
- 在配置文件中設(shè)置context.initializer.classes=com.example.demo.TestApplicationContextInitializer。
- 使用Spring的SPI擴展,在spring.factories中添加org.springframework.context.Applicatinotallow=com.example.demo.TestApplicationContextInitializer。
BeanDefinitionRegistryPostProcessor
org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
該接口是在讀取項目中的beanDefinition后執(zhí)行的,提供了一個補充的擴展點。
使用場景:你可以在此處動態(tài)注冊自定義的beanDefinition,并加載類路徑之外的Bean。
擴展方法如下:
public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanDefinitionRegistry");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanFactory");
}
}
BeanFactoryPostProcessor
org.springframework.beans.factory.config.BeanFactoryPostProcessor
該接口是對beanFactory的擴展,它的調(diào)用發(fā)生在Spring讀取完beanDefinition信息之后,Bean實例化之前。
在這個階段,用戶可以通過實現(xiàn)該擴展接口來處理某些任務(wù),如修改已注冊的beanDefinition的元數(shù)據(jù)。
擴展方法如下:
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("[BeanFactoryPostProcessor]");
}
}
InstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor
此接口繼承自BeanPostProcessor接口,區(qū)別在于:
BeanPostProcessor接口只在Bean的初始化階段(即注入Spring上下文之前和之后)擴展,而InstantiationAwareBeanPostProcessor接口增加了三個方法,擴展了Bean的實例化和屬性注入階段的作用范圍。
該類的主要擴展點是以下五個方法,它們在Bean生命周期的實例化階段和初始化階段發(fā)揮作用。按調(diào)用順序如下:
- postProcessBeforeInstantiation: 在實例化Bean之前,相當于在創(chuàng)建(new)Bean之前。
- postProcessAfterInstantiation: 在實例化Bean之后,相當于創(chuàng)建(new)Bean之后。
- postProcessPropertyValues: Bean實例化后,在屬性注入階段觸發(fā)。像@Autowired、@Resource等注解的原理就基于這個方法。
- postProcessBeforeInitialization: Bean初始化之前,相當于在Bean注入Spring上下文之前。
- postProcessAfterInitialization: Bean初始化之后,相當于在Bean注入Spring上下文之后。
使用場景:該擴展點在中間件開發(fā)和業(yè)務(wù)邏輯中都非常有用。例如,可以在Bean生命周期的不同階段收集實現(xiàn)某個接口的Bean,或為某種類型的Bean統(tǒng)一設(shè)置屬性等。
擴展方法如下:
public class TestInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] before initialization " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] after initialization " + beanName);
return bean;
}
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] before instantiation " + beanName);
return null;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] after instantiation " + beanName);
return true;
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] postProcessPropertyValues " + beanName);
return pvs;
}
SmartInstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor
該擴展接口有三個觸發(fā)方法:
- predictBeanType: 在postProcessBeforeInstantiation之前觸發(fā)(在時序圖中未標出,通常不需要擴展此點)。該方法用于預測Bean的類型,返回第一個成功預測的Class類型,如果無法預測則返回null。當調(diào)用BeanFactory.getType(name)且無法通過Bean名稱確定類型信息時,該回調(diào)方法用于決定類型信息。
- determineCandidateConstructors: 在postProcessBeforeInstantiation之后觸發(fā),用于確定Bean的構(gòu)造函數(shù),返回Bean的所有構(gòu)造函數(shù)列表。用戶可以擴展此點以自定義選擇適當?shù)臉?gòu)造函數(shù)來實例化Bean。
- getEarlyBeanReference: 在postProcessAfterInstantiation之后觸發(fā)。在存在循環(huán)依賴的場景下,Bean實例化后,為了防止循環(huán)依賴,提前暴露回調(diào)方法,用于實例化后的Bean進行后處理。
擴展方法如下:
public class TestSmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] predictBeanType " + beanName);
return beanClass;
}
@Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] determineCandidateConstructors " + beanName);
return null;
}
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] getEarlyBeanReference " + beanName);
return bean;
}
}
BeanFactoryAware
org.springframework.beans.factory.BeanFactoryAware
此類只有一個觸發(fā)點,即在Bean實例化后、屬性注入之前(即Setter方法之前)觸發(fā)。該類的擴展點方法是setBeanFactory,當用戶想要獲取當前BeanFactory的引用時,可以擴展此接口來獲取。
擴展方法如下:
public class TestBeanFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("[TestBeanFactoryAware] " + beanFactory.getBean(TestBeanFactoryAware.class).getClass().getSimpleName());
}
}
ApplicationContextAwareProcessor
org.springframework.context.support.ApplicationContextAwareProcessor
雖然這個類本身沒有擴展點,但它內(nèi)部提供了六個擴展點用于實現(xiàn)。這些擴展點是在bean實例化后、初始化之前觸發(fā)的。
圖片
正如你所看到的,這個類用于在bean實例化并填充屬性之后執(zhí)行各種驅(qū)動接口。通過執(zhí)行上述突出顯示的擴展接口,可以獲得相應(yīng)的容器變量。因此,這里實際上有六個擴展點,我將一起討論:
- EnvironmentAware: 用于獲取 EnvironmentAware 的擴展類。這個變量非常有用,可以訪問系統(tǒng)中的所有參數(shù)。個人認為,沒必要擴展這個 Aware,因為Spring內(nèi)部已經(jīng)支持通過注入直接獲取。
- EmbeddedValueResolverAware: 用于獲取 StringValueResolver 的擴展類。StringValueResolver 用于獲取基于字符串的屬性變量。通常我們使用 @Value 注解獲取這些變量,但如果實現(xiàn)了這個 Aware 接口并緩存 StringValueResolver,就可以使用它來獲取基于字符串的變量,效果相同。
- ResourceLoaderAware: 用于獲取 ResourceLoader 的擴展類。ResourceLoader 可以訪問類路徑中的所有資源對象。你可以擴展這個類來獲取 ResourceLoader 對象。
- ApplicationEventPublisherAware: 用于獲取 ApplicationEventPublisher 的擴展類。ApplicationEventPublisher 用于發(fā)布事件,通常與 ApplicationListener 結(jié)合使用,我將在后面詳細介紹。此對象也可以通過Spring注入獲得。
- MessageSourceAware: 用于獲取 MessageSource 的擴展類。MessageSource 主要用于國際化。
- ApplicationContextAware: 用于獲取 ApplicationContext 的擴展類。許多人都熟悉 ApplicationContext,它是Spring的上下文管理器,允許手動訪問Spring上下文中注冊的任何bean。我們經(jīng)常擴展這個接口來緩存Spring上下文,并將其包裝成靜態(tài)方法。此外,ApplicationContext 還實現(xiàn)了 BeanFactory、MessageSource、ApplicationEventPublisher 等接口,可以用于相關(guān)任務(wù)。
BeanNameAware
org.springframework.beans.factory.BeanNameAware
可以看出,這個類也是一種 Aware 擴展。其觸發(fā)點發(fā)生在bean初始化之前,即 postProcessBeforeInitialization 之前。這個類只有一個觸發(fā)點方法:setBeanName。
使用場景:用戶可以擴展此點,在初始化bean之前獲取Spring容器中注冊的beanName,然后根據(jù)需要修改這個beanName的值。
擴展方法:
public class NormalBeanA implements BeanNameAware{
public NormalBeanA() {
System.out.println("NormalBean constructor");
}
@Override
public void setBeanName(String name) {
System.out.println("[BeanNameAware] " + name);
}
}
@PostConstruct
javax.annotation.PostConstruct
這不是一個擴展點,而是一種標記。它的作用是在bean初始化階段。如果某個方法被 @PostConstruct 注解標記,那么該方法將首先被調(diào)用。需要注意的是,這個標準的具體觸發(fā)點是在 postProcessBeforeInitialization 之后、InitializingBean.afterPropertiesSet 之前。
使用場景:用戶可以通過注解特定方法來初始化某個特定屬性。
擴展方法:
public class NormalBeanA {
public NormalBeanA() {
System.out.println("NormalBean constructor");
}
@PostConstruct
public void init(){
System.out.println("[PostConstruct] NormalBeanA");
}
}
InitializingBean
org.springframework.beans.factory.InitializingBean
顧名思義,這個類也用于bean的初始化。InitializingBean 接口為bean提供了一個初始化方法,它只有一個方法 afterPropertiesSet。任何繼承此接口的類將在bean初始化過程中執(zhí)行此方法。此擴展的觸發(fā)點在 postProcessAfterInitialization 之前。
使用場景:用戶可以實現(xiàn)此接口,在系統(tǒng)啟動時初始化某些業(yè)務(wù)指標。
擴展方法:
public class NormalBeanA implements InitializingBean{
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("[InitializingBean] NormalBeanA");
}
}
FactoryBean
org.springframework.beans.factory.FactoryBean
在正常情況下,Spring使用反射機制和bean的類屬性來實例化bean。但在某些情況下,bean的實例化過程可能非常復雜,如果按照傳統(tǒng)方式進行,則需要在bean中配置大量信息,配置方法的靈活性有限。在這種情況下,編碼的方式可能會更簡單。為此,Spring提供了 org.springframework.beans.factory.FactoryBean 接口,允許用戶自定義bean實例化的邏輯。
FactoryBean 接口在Spring框架中具有重要地位。Spring自身提供了超過70種 FactoryBean實現(xiàn),它們隱藏了某些復雜bean實例化的細節(jié),給高級應(yīng)用帶來了方便。從Spring 3.0開始,F(xiàn)actoryBean 支持泛型,接口聲明變?yōu)?nbsp;FactoryBean<T>。
使用場景:用戶可以擴展此類,為他們希望實例化的bean創(chuàng)建代理。例如,他們可以攔截對象的所有方法,在每次調(diào)用之前和之后輸出一行日志,模擬 ProxyFactoryBean 的功能。
擴展方法:
public class TestFactoryBean implements FactoryBean<TestFactoryBean.TestFactoryInnerBean> {
@Override
public TestFactoryBean.TestFactoryInnerBean getObject() throws Exception {
System.out.println("[FactoryBean] getObject");
return new TestFactoryBean.TestFactoryInnerBean();
}
@Override
public Class<?> getObjectType() {
return TestFactoryBean.TestFactoryInnerBean.class;
}
@Override
public boolean isSingleton() {
return true;
}
public static class TestFactoryInnerBean{
}
}
SmartInitializingSingleton
org.springframework.beans.factory.SmartInitializingSingleton
這個接口只有一個方法 afterSingletonsInstantiated,其目的是作為回調(diào)接口,在Spring容器管理的所有單例對象(非延遲加載對象)初始化后調(diào)用。它的觸發(fā)點是在 postProcessAfterInitialization 之后。
使用場景:用戶可以擴展此接口,在所有單例對象完全初始化后執(zhí)行一些后處理業(yè)務(wù)。
擴展方法:
public class TestSmartInitializingSingleton implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
System.out.println("[TestSmartInitializingSingleton]");
}
}
CommandLineRunner
org.springframework.boot.CommandLineRunner
這個接口也只有一個方法:run(String... args)。它的觸發(fā)點是在整個項目啟動之后,自動執(zhí)行。如果有多個 CommandLineRunner 實例,可以使用 @Order 注解進行排序。
使用場景:用戶可以擴展此接口,在項目啟動后進行一些業(yè)務(wù)預處理。
擴展方法:
public class TestCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("[TestCommandLineRunner]");
}
}
** DisposableBean**
org.springframework.beans.factory.DisposableBean
這個擴展點也只有一個方法:destroy()。它的觸發(fā)點是在對象被銷毀時,自動執(zhí)行此方法。例如,當運行 applicationContext.registerShutdownHook 時,此方法將被觸發(fā)。
擴展方法:
public class NormalBeanA implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("[DisposableBean] NormalBeanA");
}
}
ApplicationListener
org.springframework.context.ApplicationListener
嚴格來說,這不應(yīng)被視為Spring & Spring Boot中的擴展點。ApplicationListener 可以監(jiān)聽特定的事件 (event)。觸發(fā)時機可以穿插在業(yè)務(wù)方法執(zhí)行過程中,允許用戶定義自己的業(yè)務(wù)事件。
然而,Spring內(nèi)部有一些內(nèi)置事件。這些事件可以與啟動過程交織在一起。我們也可以利用此功能,為這些內(nèi)置事件創(chuàng)建自己的監(jiān)聽器,達到與之前某些觸發(fā)點類似的效果。
讓我們列出Spring中的一些主要內(nèi)置事件:
- ContextRefreshedEvent: 當 ApplicationContext 初始化或刷新時發(fā)布此事件。這也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法時發(fā)生。這里的初始化是指所有Beans成功加載、后置處理器Beans被檢測并激活、所有單例Beans被預實例化,并且 ApplicationContext 容器已準備好使用。
- ContextStartedEvent : 當使用ConfigurableApplicationContext(ApplicationContext的子接口)中的start()方法啟動ApplicationContext時發(fā)布此事件。在spring中,您可以使用start()和stop()方法控制ApplicationContext的生命周期。啟動容器后,可以通過stop()停止容器。當容器啟動時,您可以通過getLifecycle()方法獲取所有Lifecycle接口的Bean,并激活它們的start() 方法。這通常用于具有后臺任務(wù)的Bean。
- ContextStoppedEvent : 與 ContextStartedEvent 相反,stop() 方法會觸發(fā) ContextStoppedEvent 事件。
- ContextClosedEvent: 當使用 ConfigurableApplicationContext 中的 close() 方法關(guān)閉 ApplicationContext 時,發(fā)布此事件。關(guān)閉的上下文 context 不會被重新啟動或刷新。
- RequestHandledEvent: Web應(yīng)用程序中特有的事件。它表示W(wǎng)eb請求的完成(只有在使用Spring的 DispatcherServlet 時才適用)。
- ApplicationFailedEvent: 該事件在啟動Spring Boot時遇到異常時觸發(fā)。
總結(jié)
通過這些 Spring 和 Spring Boot 的擴展點,我們可以大致了解一個 bean 的整個生命周期。在業(yè)務(wù)開發(fā)或中間件業(yè)務(wù)編寫過程中,我們可以合理利用 Spring 提供的擴展點,在 Spring 啟動的各個階段執(zhí)行特定操作,從而實現(xiàn)自定義初始化的目的。