萬字長文帶你窺探 Spring 中所有的擴展點
寫在前面
Spring 的核心思想就是容器,當容器 refresh 的時候,外部看上去風平浪靜,其實內部則是一片驚濤駭浪,汪洋一片。Springboot 更是封裝了 Spring,遵循約定大於配置,加上自動裝配的機制。很多時候我們只要引用了一個依賴,幾乎是零配置就能完成一個功能的裝配。
由 spring 提供的、在容器或 bean 生命週期各個階段、供 spring 框架回調使用的函數方法,即爲擴展點。擴展點體現了 Spring 框架的靈活性、業務親和性。使開發人員可以在不修改 spring 源碼的情況下,對容器和 bean 的行爲、屬性進行額外的管理。
想要把自動裝配玩的轉,就必須要了解 spring 對於 bean 的構造生命週期以及各個擴展接口,當然瞭解了 bean 的各個生命週期也能促進我們加深對 spring 的理解。業務代碼也能合理利用這些擴展點寫出更優雅的代碼。
在網上搜索 spring 擴展點,發現很少有博文說的很全的,只有一些常用的擴展點的說明。所以在這篇文章裏,我總結了幾乎 Spring & Springboot 所有的擴展接口,各個擴展點的使用場景,並整理出一個 bean 在 spring 中從被加載到初始化到銷燬的所有可擴展點的順序調用圖。
本文不講原理,只將擴展點與使用方式講清楚,特別是調用順序,原理可以移步 IOC 系列文章,Bean 的生命週期,後續會不斷更新對應原理及源碼解析。大家可以把這篇文章當成一個工具書,當忘了執行順序時,或者忘了如何使用這個擴展方式時,可以再回過頭來看看。
ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer
介紹
這是整個 spring 容器在刷新之前初始化ConfigurableApplicationContext
的回調接口,簡單來說,就是在容器刷新 refresh 之前調用 此類的initialize
方法。這個接口的主要目的是在 Spring 應用上下文初始化的早期階段進行一些配置或調整,以便在上下文加載後可以使用這些配置。
此接口,Spring Framework
自己沒有提供任何的實現類,但在SpringBoot
對它有較多的擴展實現。
使用場景
- 在應用啓動時進行環境配置:可以使用
ApplicationContextInitializer
來在應用上下文初始化時進行一些環境相關的配置,例如設置系統屬性、加載外部配置文件等。
public class EnvironmentInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.setActiveProfiles("development");
System.out.println("配置文件設置爲development");
}
}
- 註冊自定義的
BeanFactoryPostProcessor
或者BeanPostProcessor
:ApplicationContextInitializer
可以用來註冊自定義的BeanFactoryPostProcessor
或者BeanPostProcessor
,以便在 Bean 初始化之前或之後進行某些自定義處理。
public class CustomBeanFactoryPostProcessorInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addBeanFactoryPostProcessor(beanFactory -> {
// 添加自定義的 BeanFactoryPostProcessor
System.out.println("添加了自定義BeanFactory後處理器...");
});
}
}
- 動態地添加
PropertySource
:可以在初始化過程中動態地添加PropertySource
,以便後續的 Bean 定義和初始化過程中可以使用這些屬性。
public class PropertySourceInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
propertySources.addFirst(new MapPropertySource("customPropertySource", Collections.singletonMap("customKey", "customValue")));
System.out.println("已添加自定義屬性源");
}
}
Spring 環境下添加擴展點
在 Spring 環境下自定義實現一個ApplicationContextInitializer
讓並且它生效的方式有三種:
- 手動調用的 setXXX 方法添加
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
// Add initializer
context.addApplicationListener(new TestApplicationContextInitializer());
// Set config locations and refresh context
context.setConfigLocation("classpath:applicationContext.xml");
context.refresh();
// Use the context
// ...
context.close();
- Spring 的 XML 配置文件中註冊
<context:initializer class="com.seven.springsrpingbootextentions.extentions.TestApplicationContextInitializer"/>
- web.xml 文件配置
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.seven.springsrpingbootextentions.extentions.TestApplicationContextInitializer</param-value>
</context-param>
SpringBoot 環境下添加擴展點
示例,展示瞭如何實現一個 ApplicationContextInitializer 來添加一個自定義的屬性源:
public class TestApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
// 創建自定義的屬性源
Map<String, Object> customProperties = new HashMap<>();
customProperties.put("custom.property", "custom value");
MapPropertySource customPropertySource = new MapPropertySource("customPropertySource", customProperties);
// 將自定義屬性源添加到應用程序上下文的屬性源列表中
propertySources.addFirst(customPropertySource);
}
}
在 SpringBoot 中讓它生效的方式也有三種:
- 在啓動類中用
springApplication.addInitializers(new TestApplicationContextInitializer())
語句加入
@SpringBootApplication
public class MySpringExApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MySpringExApplication.class);
application.addInitializers(new TestApplicationContextInitializer()); // 直接在SpringApplication中添加
application.run(args);
}
}
- application 配置文件 配置
com.seven.springsrpingbootextentions.extentions.TestApplicationContextInitializer
# application.yml文件
context:
initializer:
classes: com.seven.springsrpingbootextentions.extentions.TestApplicationContextInitializer
- Spring SPI 擴展,在 spring.factories 中加入(官方推薦):
com.seven.springsrpingbootextentions.extentions.TestApplicationContextInitializer
SpringBoot 內置的 ApplicationContextInitializer
-
DelegatingApplicationContextInitializer:使用環境屬性
context.initializer.classes
指定的初始化器 (initializers) 進行初始化工作,如果沒有指定則什麼都不做。通過它使得我們可以把自定義實現類配置在application.properties
裏成爲了可能。 -
ContextIdApplicationContextInitializer:設置 Spring 應用上下文的 ID,Id 設置爲啥值會參考環境屬性:
-
spring.application.name
-
vcap.application.name
-
spring.config.name
-
spring.application.index
-
vcap.application.instance_index
-
如果這些屬性都沒有,ID 使用 application。
-
ConfigurationWarningsApplicationContextInitializer:對於一般配置錯誤在日誌中作出警告
-
ServerPortInfoApplicationContextInitializer:將內置 servlet 容器實際使用的監聽端口寫入到 Environment 環境屬性中。這樣屬性
local.server.port
就可以直接通過@Value
注入到測試中,或者通過環境屬性 Environment 獲取。 -
SharedMetadataReaderFactoryContextInitializer:創建一個 SpringBoot 和 ConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory 對象。實現類爲:ConcurrentReferenceCachingMetadataReaderFactory
-
ConditionEvaluationReportLoggingListener:將 ConditionEvaluationReport 寫入日誌。
BeanFactoryPostProcessor
org.springframework.beans.factory.config.BeanFactoryPostProcessor
介紹
這個接口是beanFactory
的擴展接口,調用時機在 spring 在讀取beanDefinition
信息之後,實例化 bean 之前。雖然此時不能再註冊 beanDefinition,但是可以趁着 bean 沒有實例化,可以修改 Spring 容器啓動時修改其內部的 BeanDefinition
。通過實現 BeanFactoryPostProcessor
接口,開發者可以在 Bean 實例化之前修改 Bean 的定義元數據,例如 Scope、依賴查找方式、初始化方法、修改屬性值、添加額外的元數據等,進而影響初始化行爲。
在應用程序啓動時,Spring 容器會自動檢測並調用所有實現了 BeanFactoryPostProcessor 接口的類的 postProcessBeanFactory 方法。開發人員可以利用這個方法來實現自定義的邏輯,從而實現一些高級的自定義邏輯和功能擴展。此方法只調用一次,同時記住不要在這裏做 Bean 的實例化。
使用場景
- 修改 Bean 屬性:可以動態地改變某些配置屬性或者注入額外的依賴。
public class PropertyModifierBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("myBean");
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
propertyValues.addPropertyValue("propertyName", "newValue");
}
}
- 動態註冊 Bean:可以根據配置文件或者系統環境變量來決定是否註冊某個 Bean。
public class ConditionalBeanRegistrar implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (someCondition()) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MyBean.class);
beanFactory.registerBeanDefinition("myConditionalBean", beanDefinitionBuilder.getBeanDefinition());
}
}
private boolean someCondition() {
// 自定義條件邏輯
return true;
}
}
- 修改 Bean 定義:可以修改 Bean 的作用域、初始化和銷燬方法等定義信息。
public class ScopeModifierBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("myBean");
beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
}
}
- 屬性佔位符替換:可以使用
PropertyPlaceholderConfigurer
實現BeanFactoryPostProcessor
接口,來替換 Bean 定義中的屬性佔位符。
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
super.processProperties(beanFactory, props);
// 自定義屬性處理邏輯
}
}
其它使用場景:
-
配置中心集成:當需要從外部配置中心(如 Spring Cloud Config 或 Apache Zookeeper)動態加載配置並修改 Bean 定義時,可以使用
BeanFactoryPostProcessor
。 -
多環境支持:根據不同的環境(如開發、測試、生產環境)動態修改 Bean 的定義,確保不同環境下的 Bean 配置有所不同。
-
動態註冊 Bean:在應用運行時,根據條件動態註冊或者取消 Bean,比如在某些特定條件下才需要註冊某些 Bean。
-
複雜業務應用:有時候會需要根據複雜的業務規則動態調整 Bean 的配置,這時候
BeanFactoryPostProcessor
非常有用。
BeanDefinitionRegistryPostProcessor
org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
介紹
BeanDefinitionRegistryPostProcessor 爲容器級後置處理器。容器級的後置處理器會在 Spring 容器初始化後、刷新前執行一次,用於動態註冊 Bean 到容器。
通過 BeanFactoryPostProcessor 的子類 BeanDefinitionRegistryPostProcessor,可以註冊一個你自己的 BeanDefinition 對象到容器中,等待容器內部依次調用進行對象實例化就能當 bean 用了。
BeanDefinitionRegistryPostProcessor 用於在 bean 解析後實例化之前通過 BeanDefinitionRegistry 對 BeanDefintion 進行增刪改查。
前文介紹的 BeanFactoryPostProcessor 是這個接口的父類,因此實現 BeanDefinitionRegistryPostProcessor 這個接口,也可以重寫其父類。但實現了 BeanDefinitionRegistryPostProcessor 的 postProcessBeanFactory 方法會先執行,再執行實現了 BeanFactoryPostProcessor 的 postProcessBeanFactory。具體看調用順序圖
使用場景
- 修改現有的 BeanDefinition:可以在 Bean 實例化之前修改現有的
BeanDefinition
,如更改其屬性值或作用域。
public class BeanDefinitionModifier implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("在 postProcessBeanDefinitionRegistry 中修改現有的 BeanDefinition");
if (registry.containsBeanDefinition("myExistingBean")) {
BeanDefinition beanDefinition = registry.getBeanDefinition("myExistingBean");
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
propertyValues.add("propertyName", "newValue");
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 此方法可以留空或用於進一步處理
}
}
- 條件性地註冊 Bean:基於某些條件(如環境變量、配置文件等)動態註冊或取消註冊某些 Bean。
public class ConditionalBeanRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("在 postProcessBeanDefinitionRegistry 中根據條件註冊 Bean");
if (someCondition()) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(ConditionalBean.class)
.getBeanDefinition();
registry.registerBeanDefinition("conditionalBean", beanDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 此方法可以留空或用於進一步處理
}
private boolean someCondition() {
// 自定義條件邏輯
return true;
}
}
- 掃描和註冊自定義註解的 Bean:實現自定義註解的掃描邏輯,並動態註冊這些註解標註的 Bean。
public class CustomAnnotationBeanRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("在 postProcessBeanDefinitionRegistry 中掃描並註冊自定義註解的 Bean");
// 自定義掃描邏輯,假設找到一個類 MyAnnotatedBean
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(MyAnnotatedBean.class)
.getBeanDefinition();
registry.registerBeanDefinition("myAnnotatedBean", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 此方法可以留空或用於進一步處理
}
}
- 比如依賴 Redis.jar,如果該依賴 jar 存在,則用 redis 當緩存,否則就用本地緩存。這個需求完全可以在 postProcessBeanDefinitionRegistry 中利用 Class.forName 判斷依賴,存在的話則註冊對應 class 到容器。
@Configuration
public class AppConfig {
@Bean
public static BeanDefinitionRegistryPostProcessor customBeanDefinitionRegistryPostProcessor() {
return new BeanDefinitionRegistryPostProcessor() {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("在 postProcessBeanDefinitionRegistry 中根據條件註冊緩存實現類");
try {
// 檢查 Redis 依賴是否存在
Class.forName("redis.clients.jedis.Jedis");
System.out.println("檢測到 Redis 依賴,註冊 RedisCacheService");
AbstractBeanDefinition redisCacheBeanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(RedisCacheService.class)
.getBeanDefinition();
registry.registerBeanDefinition("cacheService", redisCacheBeanDefinition);
} catch (ClassNotFoundException e) {
System.out.println("未檢測到 Redis 依賴,註冊 LocalCacheService");
AbstractBeanDefinition localCacheBeanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(LocalCacheService.class)
.getBeanDefinition();
registry.registerBeanDefinition("cacheService", localCacheBeanDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 此方法可以留空或用於進一步處理
}
};
}
}
- Mybatis 就是用 BeanFactoryPostProcessor 去註冊的 mapper
MapperScannerConfigurer
的主要功能是通過掃描指定的包路徑,找到所有標註了 @Mapper
註解(或其他指定註解)的接口,並將這些接口註冊爲 Spring 的 BeanDefinition。這樣,Spring 容器在啓動時會自動創建這些 Mapper 接口的代理對象,並將其注入到需要的地方。
以下是 MapperScannerConfigurer
的核心代碼片段:
// org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
private String basePackage;
private ApplicationContext applicationContext;
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//設置其資源加載器爲當前的 ApplicationContext
scanner.setResourceLoader(this.applicationContext);
scanner.registerFilters();
//調用 scanner.scan(this.basePackage) 方法,掃描指定的包路徑,找到所有符合條件的 Mapper 接口,並將它們註冊爲 Spring 的 BeanDefinition。
scanner.scan(this.basePackage);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 此方法可以留空或用於進一步處理
}
}
BeanPostProcessor
org.springframework.beans.factory.config.BeanPostProcessor
介紹
BeanPostProcessor
接口定義了兩個基本的 Bean 初始化回調方法,在屬性賦值前後執行。
-
postProcessBeforeInitialization
:在 Bean 初始化方法(如@PostConstruct
、InitializingBean.afterPropertiesSet
或自定義初始化方法)調用之前執行;返回的對象將是實際注入到容器中的 Bean,如果返回 null,則該 Bean 不會被註冊。可用於創建代理類 -
postProcessAfterInitialization
:初始化 bean 之後,返回的對象將是實際注入到容器中的 Bean,如果返回 null,則該 Bean 不會被註冊。
使用場景
- 初始化前後進行自定義邏輯:在 Bean 初始化之前或之後執行一些自定義的操作,例如設置一些屬性、進行依賴注入、執行某些檢查等。
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MyBean) {
System.out.println("bean初始化前: " + beanName);
((MyBean) bean).setName("Modified Name Before Initialization");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MyBean) {
System.out.println("bean初始化後: " + beanName);
}
return bean;
}
}
public class MyBean {
private String name;
public MyBean(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void init() {
System.out.println("bean正在init: " + name);
}
@Override
public String toString() {
return "MyBean{'}";
}
- 代理對象的生成:在
postProcessAfterInitialization
方法中生成 Bean 的代理對象,用於 AOP(面向切面編程)或其他用途。
@Component
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MyBean) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method: " + method.getName());
return result;
}
});
return enhancer.create();
}
return bean;
}
}
- 日誌記錄和監控:記錄 Bean 的初始化過程,進行性能監控、日誌記錄等。
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("開始初始化bean: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化bean結束: " + beanName);
return bean;
}
}
- 自動裝配和注入:在初始化前後進行自動裝配和注入,例如通過反射爲某些字段注入值。
@Component
public class AutowireBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(AutowireCustom.class)) {
field.setAccessible(true);
try {
field.set(bean, "Injected Value");
} catch (IllegalAccessException e) {
throw new BeansException("Failed to autowire field: " + field.getName(), e) {};
}
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
@Retention(RetentionPolicy.RUNTIME)
public @interface AutowireCustom {
}
public class MyBean {
@AutowireCustom
private String customField;
public MyBean() {
}
@Override
public String toString() {
return "MyBean{customField='" + customField + "'}";
}
}
InstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor
介紹
該接口繼承了BeanPostProcessor
接口,因爲 InstantiationAwareBeanPostProcessor 也屬於 Bean 級的後置處理器,區別如下:
BeanPostProcess
接口只在 bean 的初始化階段進行擴展(注入 spring 上下文前後),而InstantiationAwareBeanPostProcessor
接口在此基礎上增加了 3 個方法,把可擴展的範圍增加了實例化階段和屬性注入階段。
該類主要的擴展點有以下 6 個方法,其中有兩個是 BeanPostProcessor 的擴展,主要在 bean 生命週期的兩大階段:實例化階段和初始化階段,下面一起進行說明,按調用順序爲:
-
postProcessBeforeInstantiation
:在 Bean 實例化之前調用,如果返回 null,一切按照正常順序執行;如果返回的是一個實例的對象,那麼postProcessAfterInstantiation()
會執行,其他的擴展點將不再觸發。 -
postProcessAfterInstantiation
:在 Bean 實例化之後調用,可以對已實例化的 Bean 進行進一步的自定義處理。 -
postProcessPropertyValues
(方法在 spring5.1 版本後就已棄用):bean 已經實例化完成,在屬性注入時階段觸發,@Autowired
,@Resource
等註解原理基於此方法實現;可以修改 Bean 的屬性值或進行其他自定義操作,當 postProcessAfterInstantiation 返回 true 才執行。 -
postProcessBeforeInitialization
(BeanPostProcessor 的擴展):初始化 bean 之前,相當於把 bean 注入 spring 上下文之前;可用於創建代理類,如果返回的不是 null(也就是返回的是一個代理類) ,那麼後續只會調用 postProcessAfterInitialization() 方法 -
postProcessAfterInitialization
(BeanPostProcessor 的擴展):初始化 bean 之後,相當於把 bean 注入 spring 上下文之後;返回值會影響 postProcessProperties() 是否執行,其中返回 false 的話,是不會執行。 -
postProcessProperties()
:在 Bean 設置屬性前調用;用於修改 bean 的屬性,如果返回值不爲空,那麼會更改指定字段的值
注意:InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 是可以同時被實現的,並且也會同時生效,但是 InstantiationAwareBeanPostProcessor 的執行時機要稍早於 BeanPostProcessor;具體看上面調用順序圖
InstantiationAwareBeanPostProcessor
提供了更細粒度的控制,可以在 Bean 的實例化和屬性設置過程中插入自定義邏輯。無論是替換默認的實例化過程、控制依賴注入,還是修改屬性值,InstantiationAwareBeanPostProcessor
都提供了強大的靈活性和可擴展性,使得開發者可以在 Bean 的生命週期中進行更精細的控制。
使用場景
- 在實例化之前替換 Bean:替換默認的 Bean 實例化過程,可能是返回一個代理對象。
@Component
public class CustomInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if (beanClass == MyBean.class) {
System.out.println("實例化之前替換 Bean: " + beanName);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(beanClass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("調用方法: " + method.getName());
return proxy.invokeSuper(obj, args);
}
});
return enhancer.create();
}
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之後的 Bean: " + beanName);
return bean;
}
}
- 控制實例化後的依賴注入過程:在實例化後但在依賴注入之前進行一些自定義邏輯。
@Component
public class DependencyInjectionControlPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if (bean instanceof MyBean) {
System.out.println("實例化之後控制依賴注入: " + beanName);
return false; // 不進行默認的依賴注入
}
return true;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之後的 Bean: " + beanName);
return bean;
}
}
- 修改屬性值:在屬性值設置過程中進行干預,修改或添加屬性值。
@Component
public class PropertyModificationPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
if (bean instanceof MyBean) {
System.out.println("設置屬性值之前: " + beanName);
// 修改屬性值的邏輯
}
return pvs;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之後的 Bean: " + beanName);
return bean;
}
}
SmartInstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor
介紹
SmartInstantiationAwareBeanPostProcessor
與其他擴展點最明顯的不同,就是在實際的業務開發場景中應用到的機會並不多,主要是在 Spring 內部應用。
該擴展接口有 3 個觸發點方法:
-
predictBeanType
:該觸發點發生在postProcessBeforeInstantiation
之前 (也就是在 InstantiationAwareBeanPostProcessor 的方法之前,在圖上並沒有標明,因爲一般不太需要擴展這個點),這個方法用於預測 Bean 的類型,返回第一個預測成功的 Class 類型,如果不能預測,則返回 null;當調用BeanFactory.getType(name)
時當通過 bean 的名字無法得到 bean 類型信息時就調用該回調方法來決定類型信息。 -
determineCandidateConstructors
:該觸發點發生在postProcessBeforeInstantiation
之後,用於決定使用哪個構造器構造 Bean,返回的是該 bean 的所有構造函數列表;如果不指定,默認爲 null,即 bean 的無參構造方法。用戶可以擴展這個點,來自定義選擇相應的構造器來實例化這個 bean。 -
getEarlyBeanReference
:該觸發點發生在postProcessAfterInstantiation
之後,主要用於 Spring 循環依賴問題的解決,如果 Spring 中檢測不到循環依賴,這個方法不會被調用;當存在 Spring 循環依賴這種情況時,當 bean 實例化好之後,爲了防止有循環依賴,會提前暴露回調方法,用於 bean 實例化的後置處理,會在 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法觸發執行之後執行;
注意:同 InstantiationAwareBeanPostProcessor,由於 SmartInstantiationAwareBeanPostProcessor 是 InstantiationAwareBeanPostProcessor 的子類,因此也 SmartInstantiationAwareBeanPostProcessor 也同樣能擴展 InstantiationAwareBeanPostProcessor 的所有方法。但是如果有兩個類分別重寫了 SmartInstantiationAwareBeanPostProcessor 和 InstantiationAwareBeanPostProcessor 的方法,那麼重寫 InstantiationAwareBeanPostProcessor 的類的方法 會先於 重寫了 SmartInstantiationAwareBeanPostProcessor 的類的方法(注意,這裏說的是兩者都有的方法)。
使用場景
- 自定義構造函數選擇:在實例化 Bean 時,選擇特定的構造函數。
@Component
public class CustomConstructorSelectionPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
if (beanClass == MyBean.class) {
System.out.println("選擇自定義構造函數: " + beanName);
try {
return new Constructor<?>[] { beanClass.getConstructor(String.class) };
} catch (NoSuchMethodException e) {
throw new BeansException("找不到指定的構造函數", e) {};
}
}
return null;
}
}
public class MyBean {
private String name;
public MyBean() {
this.name = "Default Name";
}
public MyBean(String name) {
this.name = name;
}
@Override
public String toString() {
return "MyBean{'}";
}
}
- 解決循環依賴問題:通過提供早期 Bean 引用,解決循環依賴問題。
@Component
public class EarlyBeanReferencePostProcessor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
if (bean instanceof MyBean) {
System.out.println("獲取早期 Bean 引用: " + beanName);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("調用方法: " + method.getName());
return proxy.invokeSuper(obj, args);
}
});
return enhancer.create();
}
return bean;
}
}
- 預測 Bean 類型:在 Bean 實例化之前,預測 Bean 的類型。
@Component
public class BeanTypePredictionPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
if (beanClass == MyBean.class) {
System.out.println("預測 Bean 類型: " + beanName);
return MyBean.class;
}
return null;
}
}
MergedBeanDefinitionPostProcessor
org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor
介紹
MergedBeanDefinitionPostProcessor 繼承自 BeanPostProcessor。調用的時機是,在實例化之後進行的調用,只要是收集 bean 上的屬性的,比如收集標記了某些註解的字段或者方法,都可以基於 MergedBeanDefinitionPostProcessor 來進行擴展。
對於不同方式導入的 Bean 定義,如果存在重複對同一個 Bean 的定義,則會根據 allowBeanDefinitionOverriding 屬性是否設置爲 true,判斷是否允許 Bean 定義的覆蓋,如果不允許,則拋出異常。而在 Bean 實例化之前,會進行 BeanDefinition 類型的歸一化,即 mergeBeanFintion ,統一轉換爲 RootBeanfintion 進行後續處理。當然,這裏的 merge 更多指代的是父子 Bean 定義的合併。
也用於收集 bean 上的註解,比如常見的 @Value、@NacosValue、@Mapper 等,再將收集好的數據緩存在 injectionMetadataCache 中,以便後續比如屬性注入的時候使用。
該接口有兩個擴展方法:
-
postProcessMergedBeanDefinition
:此方法在 Spring 將多個 Bean 定義合併爲一個RootBeanDefinition
之後,但在實例化 Bean 之前被調用。主要作用是讓開發者有機會在 Bean 定義合併後,對其進行進一步的定製和調整。使用場景如下: -
自定義註解處理:處理自定義註解並將其應用於 Bean 定義。
-
屬性修改:在 Bean 實例化之前對 Bean 定義中的某些屬性進行調整或設置默認值。
-
resetBeanDefinition
:此方法在 Bean 定義被重置時調用。它通常用於清理或重置與特定 Bean 定義相關的狀態或緩存。使用場景如下: -
狀態清理:清理緩存或臨時狀態,以便 Bean 定義可以被重新解析。
-
重置自定義元數據:在 Bean 定義被重置時,重置自定義的元數據或狀態。
使用場景
- 對合並後的 Bean 定義信息進行修改:在 Bean 實例化之前,修改其定義信息,例如添加屬性值或修改構造函數參數。
@Component
public class CustomMergedBeanDefinitionPostProcessor implements MergedBeanDefinitionPostProcessor, BeanPostProcessor {
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (beanType == MyBean.class) {
System.out.println("合併後的 Bean 定義信息, Bean 名稱: " + beanName);
// 修改合併後的 Bean 定義信息
beanDefinition.getPropertyValues().add("name", "修改後的名稱");
}
}
@Override
public void resetBeanDefinition(String beanName) {
System.out.println("重置 Bean 定義信息, Bean 名稱: " + beanName);
// 實現重置邏輯
}
}
public class MyBean {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "MyBean{'}";
}
}
- 實現通用的自定義邏輯:在所有 Bean 實例化之前,執行一些通用的自定義邏輯。
@Component
public class CommonLogicMergedBeanDefinitionPostProcessor implements MergedBeanDefinitionPostProcessor, BeanPostProcessor {
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
System.out.println("處理合併後的 Bean 定義信息, Bean 名稱: " + beanName);
// 添加通用的自定義邏輯,例如給所有 Bean 添加某個屬性
beanDefinition.getPropertyValues().add("commonProperty", "通用屬性值");
}
@Override
public void resetBeanDefinition(String beanName) {
System.out.println("重置 Bean 定義信息, Bean 名稱: " + beanName);
// 實現重置邏輯
}
}
public class MyBean {
private String name;
private String commonProperty;
public void setName(String name) {
this.name = name;
}
public void setCommonProperty(String commonProperty) {
this.commonProperty = commonProperty;
}
@Override
public String toString() {
return "MyBean{'}";
}
}
- 條件性地重置 Bean 定義信息:在某些條件下重置 Bean 的定義信息,使得下一次的實例化可以使用更新後的定義信息。
@Component
public class ConditionalResetMergedBeanDefinitionPostProcessor implements MergedBeanDefinitionPostProcessor, BeanPostProcessor {
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
System.out.println("處理合併後的 Bean 定義信息, Bean 名稱: " + beanName);
// 這裏可以根據條件決定是否修改 Bean 定義
if (beanName.equals("conditionalBean")) {
beanDefinition.getPropertyValues().add("name", "重置後的名稱");
}
}
@Override
public void resetBeanDefinition(String beanName) {
System.out.println("重置 Bean 定義信息, Bean 名稱: " + beanName);
// 這裏可以實現條件性重置邏輯
}
}
public class ConditionalBean {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "ConditionalBean{'}";
}
}
BeanNameAware
org.springframework.beans.factory.BeanNameAware
介紹
這個類是 Aware 擴展的一種,觸發點在 bean 的初始化之前,也就是postProcessBeforeInitialization
之前,這個類的觸發點方法只有一個:setBeanName
。
用於讓 Bean 獲得其在 Spring 容器中的名稱。實現了 BeanNameAware
接口的 Bean 可以在初始化時獲得自身的 Bean 名稱,這在某些需要根據 Bean 名稱進行邏輯處理的場景非常有用。
使用場景
- 記錄或日誌輸出 Bean 名稱:在某些應用場景中,開發者可能希望在 Bean 初始化時記錄或輸出 Bean 的名稱。這對調試和日誌記錄非常有幫助。
@Component
public class LoggingBean implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("設置 Bean 名稱: " + name);
}
public void doSomething() {
System.out.println("正在執行某些操作, 當前 Bean 名稱: " + beanName);
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
LoggingBean loggingBean = context.getBean(LoggingBean.class);
loggingBean.doSomething();
}
}
- 根據 Bean 名稱實現條件性邏輯:有時,一個 Bean 可能需要根據其名稱決定執行不同的邏輯。例如,可以在初始化過程或某些方法調用中根據 Bean 名稱執行特定操作。
@Component
public class ConditionalLogicBean implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("設置 Bean 名稱: " + name);
}
public void performAction() {
if ("conditionalLogicBean".equals(beanName)) {
System.out.println("執行特定邏輯, 因爲這是 conditionalLogicBean");
} else {
System.out.println("執行普通邏輯");
}
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ConditionalLogicBean conditionalLogicBean = context.getBean(ConditionalLogicBean.class);
conditionalLogicBean.performAction();
}
}
- 動態註冊多個同類型的 Bean:在某些複雜的應用場景中,可能需要動態註冊多個同類型的 Bean,並且需要根據名稱區分它們。實現
BeanNameAware
接口可以很方便地獲取和使用這些 Bean 的名稱。
@Component("beanA")
public class DynamicBeanA implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("設置 Bean 名稱: " + name);
}
public void execute() {
System.out.println("執行 Bean: " + beanName);
}
}
@Component("beanB")
public class DynamicBeanB implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("設置 Bean 名稱: " + name);
}
public void execute() {
System.out.println("執行 Bean: " + beanName);
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
DynamicBeanA beanA = (DynamicBeanA) context.getBean("beanA");
DynamicBeanB beanB = (DynamicBeanB) context.getBean("beanB");
beanA.execute();
beanB.execute();
}
}
BeanClassLoaderAware
org.springframework.beans.factory.BeanClassLoaderAware
介紹
用於讓一個 Bean 獲取到加載它的 ClassLoader
。實現這個接口的 Bean 會在其屬性設置完成後、初始化方法調用之前被注入 ClassLoader
。該接口定義了一個方法:
void setBeanClassLoader(ClassLoader classLoader)
:在某些需要動態加載類的場景中,獲取ClassLoader
是非常有用的。
使用場景
- 動態加載類:有時候,我們可能需要在運行時動態加載類,利用
BeanClassLoaderAware
可以方便地獲取到ClassLoader
來實現這一需求。
@Component
public class DynamicClassLoader implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("已設置類加載器");
}
public void loadClass(String className) {
try {
Class<?> clazz = classLoader.loadClass(className);
System.out.println("已加載類:" + clazz.getName());
} catch (ClassNotFoundException e) {
System.out.println("類未找到:" + className);
}
}
}
@SpringBootApplication
public class AppConfig {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(AppConfig.class, args);
DynamicClassLoader dynamicClassLoader = context.getBean(DynamicClassLoader.class);
dynamicClassLoader.loadClass("java.util.ArrayList");
dynamicClassLoader.loadClass("不存在的類");
}
}
- 檢查類的可用性:在某些情況下,我們可能需要檢查某個類是否在當前的類路徑中可用。利用
BeanClassLoaderAware
可以方便地實現這一需求。
@Component
public class ClassAvailabilityChecker implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("已設置類加載器");
}
public boolean isClassAvailable(String className) {
try {
Class<?> clazz = classLoader.loadClass(className);
System.out.println("類可用:" + clazz.getName());
return true;
} catch (ClassNotFoundException e) {
System.out.println("類不可用:" + className);
return false;
}
}
}
@SpringBootApplication
public class AppConfig {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(AppConfig.class, args);
ClassAvailabilityChecker checker = context.getBean(ClassAvailabilityChecker.class);
checker.isClassAvailable("java.util.HashMap");
checker.isClassAvailable("不存在的類");
}
}
- 加載資源文件:通過
BeanClassLoaderAware
獲取的ClassLoader
,我們還可以方便地加載資源文件。
@Component
public class ResourceLoader implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("已設置類加載器");
}
public void loadResource(String resourcePath) {
InputStream inputStream = classLoader.getResourceAsStream(resourcePath);
if (inputStream != null) {
System.out.println("資源已加載:" + resourcePath);
} else {
System.out.println("資源未找到:" + resourcePath);
}
}
}
@SpringBootApplication
public class AppConfig {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(AppConfig.class, args);
ResourceLoader resourceLoader = context.getBean(ResourceLoader.class);
resourceLoader.loadResource("application.properties");
resourceLoader.loadResource("不存在的資源");
}
}
BeanFactoryAware
org.springframework.beans.factory.BeanFactoryAware
介紹
這個類只有一個觸發點,發生在 bean 的實例化之後,注入屬性之前,也就是 Setter 之前。這個類的擴展點方法爲setBeanFactory
,可以拿到BeanFactory
這個屬性,從而能夠進行更復雜的 Bean 操作。例如,動態獲取其他 Bean、檢查 Bean 的狀態等。
使用場景
- 動態獲取其他 Bean:通過實現
BeanFactoryAware
接口,一個 Bean 可以在運行時動態獲取其他 Bean。這在一些需要解耦的場景下非常有用。
@Component
public class DynamicBeanFetcher implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("注入 BeanFactory 實例");
}
public void fetchAndUseBean() {
MyBean myBean = beanFactory.getBean(MyBean.class);
System.out.println("獲取到的 Bean 實例: " + myBean);
}
}
@Component
public class MyBean {
@Override
public String toString() {
return "這是 MyBean 實例";
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
DynamicBeanFetcher fetcher = context.getBean(DynamicBeanFetcher.class);
fetcher.fetchAndUseBean();
}
}
- 檢查 Bean 的狀態:通過
BeanFactoryAware
,可以在運行時檢查某個 Bean 是否存在或者其狀態,這對一些需要動態檢查 Bean 狀態的場景非常有用。
@Component
public class BeanStateChecker implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("注入 BeanFactory 實例");
}
public void checkBeanState() {
boolean exists = beanFactory.containsBean("myBean");
System.out.println("MyBean 是否存在: " + exists);
}
}
@Component("myBean")
public class MyBean {
@Override
public String toString() {
return "這是 MyBean 實例";
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
BeanStateChecker checker = context.getBean(BeanStateChecker.class);
checker.checkBeanState();
}
}
- 創建複雜 Bean 的初始化邏輯:在一些複雜的業務場景中,有時需要在 Bean 初始化時執行一些複雜的邏輯,例如動態創建其他 Bean 並注入到當前 Bean 中。通過
BeanFactoryAware
可以實現這一點。
@Component
public class ComplexBeanInitializer implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("注入 BeanFactory 實例");
}
public void initializeComplexBean() {
MyBean myBean = beanFactory.getBean(MyBean.class);
System.out.println("初始化複雜 Bean, 獲取到的 MyBean 實例: " + myBean);
// 在這裏可以執行復雜的初始化邏輯
}
}
@Component
public class MyBean {
@Override
public String toString() {
return "這是 MyBean 實例";
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ComplexBeanInitializer initializer = context.getBean(ComplexBeanInitializer.class);
initializer.initializeComplexBean();
}
}
ApplicationContextAwareProcessor
org.springframework.context.support.ApplicationContextAwareProcessor
介紹
該類本身並沒有擴展點,而是 BeanPostProcessor 擴展接口的具體實現,但是該類內部卻有 6 個擴展點可供實現 ,這些擴展點的觸發時機在 bean 實例化之後,初始化之前。
可以看到,該類用於執行各種驅動接口,在 bean 實例化之後,屬性填充之後。其內部有 6 個擴展點可供實現,這幾個接口都是 Spring 預留的重點擴展實現,與 Spring 的 Bean 的生命週期 密切相關,以下按照擴展點調用順序介紹:
-
EnvironmentAware
:用於獲取EnviromentAware
的一個擴展類,這個變量非常有用, 可以獲得系統內的所有參數;另外也可以通過注入的方式來獲得 Environment,用哪種方式需要以實現場景而決定。當然個人認爲這個 Aware 沒必要去擴展,因爲 spring 內部都可以通過注入的方式來直接獲得。 -
EmbeddedValueResolverAware
:用於獲取StringValueResolver
的一個擴展類,StringValueResolver
可以獲取基於 String 類型的 properties 的變量;但一般我們都用@Value
的方式來獲取 properties 的變量,用哪種方式需要以實現場景而決定。如果實現了這個 Aware 接口,把StringValueResolver
緩存起來,通過這個類去獲取String
類型的變量,效果是一樣的。 -
ResourceLoaderAware
:用於獲取ResourceLoader
的一個擴展類,ResourceLoader
可以用於獲取 classpath 內所有的資源對象。 -
ApplicationEventPublisherAware
:用於獲取ApplicationEventPublisher
的一個擴展類,ApplicationEventPublisher
可以用來發布事件;這個對象也可以通過 spring 注入的方式來獲得,結合ApplicationListener
來共同使用,下文在介紹ApplicationListener
時會詳細提到。 -
MessageSourceAware
:用於獲取MessageSource
的一個擴展類,MessageSource
主要用來做國際化。 -
ApplicationContextAware
:用來獲取ApplicationContext
的一個擴展類,ApplicationContext
就是 spring 上下文管理器,可以手動的獲取任何在 spring 上下文註冊的 bean。較多的做法是擴展這個接口來緩存 spring 上下文,包裝成靜態方法。 同時ApplicationContext
也實現了BeanFactory
,MessageSource
,ApplicationEventPublisher
等接口,也可以用來做相關接口的事情。
使用場景
- 動態獲取其他 Bean:通過實現
ApplicationContextAware
接口,Bean 可以在運行時動態獲取其他 Bean,這在一些需要解耦的場景下非常有用。
@Component
public class DynamicBeanFetcher implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("注入 ApplicationContext 實例");
}
public void fetchAndUseBean() {
MyBean myBean = applicationContext.getBean(MyBean.class);
System.out.println("獲取到的 Bean 實例: " + myBean);
}
}
@Component
public class MyBean {
@Override
public String toString() {
return "這是 MyBean 實例";
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
DynamicBeanFetcher fetcher = context.getBean(DynamicBeanFetcher.class);
fetcher.fetchAndUseBean();
}
}
- 使用 ApplicationContext 進行事件發佈:在一些場景中,Bean 可能需要發佈事件。通過實現
ApplicationContextAware
接口,可以方便地獲取ApplicationContext
實例併發布事件。
@Component
public class EventPublisherBean implements ApplicationContextAware, ApplicationEventPublisherAware {
private ApplicationContext applicationContext;
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("注入 ApplicationContext 實例");
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void publishCustomEvent(String message) {
CustomEvent customEvent = new CustomEvent(this, message);
eventPublisher.publishEvent(customEvent);
System.out.println("發佈自定義事件: " + message);
}
}
public class CustomEvent extends ApplicationEvent {
private String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
@Component
public class CustomEventListener {
@EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("接收到自定義事件: " + event.getMessage());
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
EventPublisherBean publisher = context.getBean(EventPublisherBean.class);
publisher.publishCustomEvent("這是一個自定義事件消息");
}
}
- 獲取環境信息:通過實現
ApplicationContextAware
接口,Bean 可以訪問ApplicationContext
,並從中獲取環境配置信息,例如讀取配置文件中的屬性值。
@Component
public class EnvironmentAwareBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("注入 ApplicationContext 實例");
}
public void printEnvironmentProperty() {
Environment environment = applicationContext.getEnvironment();
String propertyValue = environment.getProperty("example.property");
System.out.println("讀取到的環境屬性值: " + propertyValue);
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
@PropertySource("classpath:application.properties")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
EnvironmentAwareBean environmentAwareBean = context.getBean(EnvironmentAwareBean.class);
environmentAwareBean.printEnvironmentProperty();
}
}
@PostConstruct
javax.annotation.PostConstruct
介紹
可以看出來其本身不是 Spring 定義的註解,但是 Spring 提供了具體的實現。這個並不算一個擴展點,其實就是一個標註。其作用是在 bean 的初始化階段,如果對一個方法標註了@PostConstruct
,會先調用這個方法。這裏重點是要關注下這個標準的觸發點,這個觸發點是在postProcessBeforeInitialization
之後,InitializingBean.afterPropertiesSet
之前。
注意:
-
使用 @PostConstruct 註解標記的方法不能有參數,除非是攔截器,可以採用攔截器規範定義的 InvocationContext 對象。
-
使用 @PostConstruct 註解標記的方法不能有返回值,實際上如果有返回值,也不會報錯,但是會忽略掉;
-
使用 @PostConstruct 註解標記的方法不能被 static 修飾,但是 final 是可以的;
使用場景
使用場景與 InitializingBean 類似,具體看下文
InitializingBean
org.springframework.beans.factory.InitializingBean
介紹
這個類,顧名思義,也是用來初始化 bean 的。InitializingBean
接口爲 bean 提供了初始化方法的方式,它只在 bean 實例化、屬性注入後的提供了一個擴展點afterPropertiesSet
方法,凡是繼承該接口的類,在初始前、屬性賦值後,都會執行該方法。這個擴展點的觸發時機在postProcessAfterInitialization
之前。
注意:
-
與 InitializingBean#afterPropertiesSet() 類似效果的是
init-method
,但是需要注意的是 InitializingBean#afterPropertiesSet() 執行時機要略早於 init-method; -
InitializingBean#afterPropertiesSet() 的調用方式是在 bean 初始化過程中真接調用 bean 的 afterPropertiesSet();
-
bean 自定義屬性 init-method 是通過 java 反射的方式進行調用 ;
使用場景
- 初始化資源:可以在 Bean 初始化後自動啓動一些資源,如數據庫連接、文件讀取等。
public class NormalBeanA implements InitializingBean{
@Overrideimport org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class ResourceInitializer implements InitializingBean {
@Override
public void afterPropertiesSet() {
// 模擬資源初始化
System.out.println("資源初始化:建立數據庫連接");
}
public void performAction() {
System.out.println("資源使用:執行數據庫操作");
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ResourceInitializer initializer = context.getBean(ResourceInitializer.class);
initializer.performAction();
}
}
- 設置初始值
@Component
public class InitialValueSetter implements InitializingBean {
private String initialValue;
@Override
public void afterPropertiesSet() {
initialValue = "默認值";
System.out.println("設置初始值:" + initialValue);
}
public void printValue() {
System.out.println("當前值:" + initialValue);
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
InitialValueSetter valueSetter = context.getBean(InitialValueSetter.class);
valueSetter.printValue();
}
}
- 加載配置:可以在 Bean 初始化後加載必要的配置,如從文件或數據庫中讀取配置。
@Component
public class ConfigLoader implements InitializingBean {
private String configValue;
@Override
public void afterPropertiesSet() {
// 模擬配置加載
configValue = "配置值";
System.out.println("加載配置:" + configValue);
}
public void printConfig() {
System.out.println("當前配置:" + configValue);
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ConfigLoader configLoader = context.getBean(ConfigLoader.class);
configLoader.printConfig();
}
}
SmartInitializingSingleton
org.springframework.beans.factory.SmartInitializingSingleton
介紹
這個接口中只有一個方法afterSingletonsInstantiated
,其作用是是 在 spring 容器管理的所有單例對象(非懶加載對象)初始化完成之後調用的回調接口。其觸發時機爲postProcessAfterInitialization
之後。
注意:
-
實現 SmartInitializingSingleton 接口的 bean 的作用域必須是單例,afterSingletonsInstantiated() 纔會觸發;
-
afterSingletonsInstantiated() 觸發執行時,非懶加載的單例 bean 已經完成實現化、屬性注入以及相關的初始化操作;
-
afterSingletonsInstantiated() 的執行時機是在 DefaultListableBeanFactory#preInstantiateSingletons();
使用場景
- 全局初始化操作:可以在所有單例 Bean 初始化後執行一些全局性的初始化操作,比如設置緩存、啓動全局調度任務等。
@Component
public class GlobalInitializer implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
// 模擬全局初始化操作
System.out.println("全局初始化操作:啓動全局調度任務");
}
}
-
檢查系統狀態:可以用於在所有單例 Bean 初始化之後檢查系統狀態,確保系統運行在預期狀態下。
-
加載全局配置:可以在所有單例 Bean 初始化後加載全局配置,如從文件或數據庫中讀取配置,並應用到系統中。
FactoryBean
org.springframework.beans.factory.FactoryBean
介紹
一般情況下,Spring 通過反射機制利用 bean 的 class 屬性指定支線類去實例化 bean,在某些情況下,實例化 Bean 過程比較複雜,如果按照傳統的方式,則需要在 bean 中提供大量的配置信息。Spring 爲此提供了一個org.springframework.bean.factory.FactoryBean
的工廠類接口,用戶可以通過實現該接口定製實例化 Bean 的邏輯。FactoryBean
接口對於 Spring 框架來說佔有重要的地位,Spring 自身就提供了 70 多個FactoryBean
的實現。它們隱藏了實例化一些複雜 bean 的細節,給上層應用帶來了便利。
觸發點:例如其他框架技術與 Spring 集成的時候,如 mybatis 與 Spring 的集成,mybatis 是通過 SqlSessionFactory 創建出 Sqlsession 來執行 sql 的,那麼 Service 層在調用 Dao 層的接口來執行數據庫操作時肯定得持有 SqlSessionFactory,那麼問題來了:Spring 容器怎麼才能持有 SqlSessionFactory 呢?答案就是 SqlSessionFactoryBean,它實現了 FactoryBean 接口。
FactoryBean 與 BeanFactory 的區別
-
FactoryBean 接口有三個方法:
-
getObject():用於獲取 bean,主要應用在創建一些複雜的 bean 的場景;
-
getObjectType():返回這個工廠創建的 Bean 實例的類型。
-
isSingleton():用於判斷返回 bean 是否屬於單例,默認 trure,通俗說就是工廠 bean;
-
BeanFactory 是 Spring bean 容器的根接口,ApplicationContext 是 Spring bean 容器的高級接口,繼承於 BeanFactory,通俗理解就是生成 bean 的工廠;
使用場景
- 創建複雜對象:使用
FactoryBean
可以幫助我們創建那些需要複雜配置或初始化的對象。
class ComplexObject {
private String name;
private int value;
public ComplexObject(String name, int value) {
this.name = name;
this.value = value;
}
@Override
public String toString() {
return "ComplexObject{}";
}
}
@Component
public class ComplexObjectFactoryBean implements FactoryBean<ComplexObject> {
@Override
public ComplexObject getObject() {
// 創建複雜對象
ComplexObject complexObject = new ComplexObject("複雜對象", 42);
System.out.println("創建複雜對象:" + complexObject);
return complexObject;
}
@Override
public Class<?> getObjectType() {
return ComplexObject.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ComplexObject complexObject = context.getBean(ComplexObject.class);
System.out.println("獲取複雜對象:" + complexObject);
}
}
- 動態切換實現:假設我們需要根據某些條件動態切換 Bean 的具體實現類,可以使用
FactoryBean
。
interface Service {
void execute();
}
class ServiceImplA implements Service {
@Override
public void execute() {
System.out.println("執行服務實現A");
}
}
class ServiceImplB implements Service {
@Override
public void execute() {
System.out.println("執行服務實現B");
}
}
@Component
public class DynamicServiceFactoryBean implements FactoryBean<Service> {
private boolean useServiceA = true; // 可以通過配置或條件動態設置
@Override
public Service getObject() {
if (useServiceA) {
System.out.println("創建服務實現A");
return new ServiceImplA();
} else {
System.out.println("創建服務實現B");
return new ServiceImplB();
}
}
@Override
public Class<?> getObjectType() {
return Service.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Service service = context.getBean(Service.class);
service.execute();
}
}
- 延遲初始化:
FactoryBean
可以用於延遲初始化某些 Bean,只有在第一次獲取時才進行實例化。
class LazyObject {
public LazyObject() {
System.out.println("懶對象被創建");
}
public void doSomething() {
System.out.println("懶對象執行操作");
}
}
@Component
public class LazyObjectFactoryBean implements FactoryBean<LazyObject> {
@Override
public LazyObject getObject() {
System.out.println("創建懶對象實例");
return new LazyObject();
}
@Override
public Class<?> getObjectType() {
return LazyObject.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
@Configuration
@ComponentScan(basePackages = "com.seven")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("獲取懶對象實例前");
LazyObject lazyObject = context.getBean(LazyObject.class);
System.out.println("獲取懶對象實例後");
lazyObject.doSomething();
}
}
CommandLineRunner 和 ApplicationRunner
org.springframework.boot.CommandLineRunner
介紹
這兩個是 Springboot 中新增的擴展點,之所以將這兩個擴展點放在一起,是因爲它兩個功能特性高度相似,不同的只是名字、擴展方法形參數類型、執行先後的一些小的不同。
這兩個接口觸發時機爲整個項目啓動完畢後,自動執行。如果有多個CommandLineRunner
,可以利用@Order
來進行排序。
注意:
-
CommandLineRunner 和 ApplicationRunner 都有一個擴展方法 run(),但是 run() 形參數類型不同;
-
CommandLineRunner.run() 方法的形參數類型是 String... args,ApplicationRunner.run() 的形參數類型是 ApplicationArguments args;
-
CommandLineRunner.run() 的執行時機要晚於 ApplicationRunner.run() 一點;
-
CommandLineRunner 和 ApplicationRunner 觸發執行時機是在 Spring 容器、Tomcat 容器正式啓動完成後,可以正式處理業務請求前,即項目啓動的最後一步;
-
CommandLineRunner 和 ApplicationRunner 可以應用的場景:項目啓動前,熱點數據的預加載、清除臨時文件、讀取自定義配置信息等;
使用場景
- 初始化數據:使用
CommandLineRunner
可以在應用啓動後初始化一些必要的數據,例如從數據庫加載某些配置或插入初始數據。
@Component
public class DataInitializer implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("初始化數據:插入初始數據");
// 模擬插入初始數據
insertInitialData();
}
private void insertInitialData() {
System.out.println("插入數據:用戶表初始數據");
}
}
- 啓動後執行任務:使用
CommandLineRunner
可以在應用啓動後執行一些特定的任務,比如發送一個通知或啓動一些背景任務。
@Component
public class TaskExecutor implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("啓動後執行任務:發送啓動通知");
// 模擬發送啓動通知
sendStartupNotification();
}
private void sendStartupNotification() {
System.out.println("通知:應用已啓動");
}
}
- 讀取命令行參數:使用
CommandLineRunner
可以獲取並處理命令行參數,這對於需要根據啓動參數動態配置應用的場景非常有用。
@Component
public class CommandLineArgsProcessor implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("處理命令行參數:");
for (String arg : args) {
System.out.println("參數:" + arg);
}
}
}
@SpringBootApplication
public class AppConfig {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, new String[]{"參數1", "參數2", "參數3"});
}
}
ApplicationListener 和 ApplicationContextInitializer
org.springframework.context.ApplicationListener
介紹
準確的說,這個應該不算 spring&springboot 當中的一個擴展點,ApplicationListener
可以監聽某個事件的event
,觸發時機可以穿插在業務方法執行過程中,用戶可以自定義某個業務事件。但是 spring 內部也有一些內置事件,這種事件,可以穿插在啓動調用中。我們也可以利用這個特性,來自己做一些內置事件的監聽器來達到和前面一些觸發點大致相同的事情。
接下來羅列下 spring 主要的內置事件:
-
ContextRefreshedEvent ApplicationContext 被初始化或刷新時,該事件被髮布。這也可以在
ConfigurableApplicationContext
接口中使用refresh()
方法來發生。此處的初始化是指:所有的 Bean 被成功裝載,後處理 Bean 被檢測並激活,所有 Singleton Bean 被預實例化,ApplicationContext
容器已就緒可用。 -
ContextStartedEvent 當使用
ConfigurableApplicationContext
(ApplicationContext 子接口)接口中的 start() 方法啓動ApplicationContext
時,該事件被髮布。你可以調查你的數據庫,或者你可以在接受到這個事件後重啓任何停止的應用程序。 -
ContextStoppedEvent 當使用
ConfigurableApplicationContext
接口中的stop()
停止ApplicationContext
時,發佈這個事件。你可以在接受到這個事件後做必要的清理的工作 -
ContextClosedEvent 當使用
ConfigurableApplicationContext
接口中的close()
方法關閉ApplicationContext
時,該事件被髮布。一個已關閉的上下文到達生命週期末端;它不能被刷新或重啓 -
RequestHandledEvent 這是一個 web-specific 事件,告訴所有 bean HTTP 請求已經被服務。只能應用於使用 DispatcherServlet 的 Web 應用。在使用 Spring 作爲前端的 MVC 控制器時,當 Spring 處理用戶請求結束後,系統會自動觸發該事件
使用場景
- 監聽自定義事件:使用
ApplicationListener
可以監聽和處理自定義事件。
// 定義自定義事件
class CustomEvent extends ApplicationEvent {
private final String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
// 監聽自定義事件
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
System.out.println("監聽到自定義事件:處理事件");
handleCustomEvent(event);
}
private void handleCustomEvent(CustomEvent event) {
System.out.println("處理自定義事件:" + event.getMessage());
}
}
@Component
public class EventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void publishCustomEvent(final String message) {
System.out.println("發佈自定義事件:" + message);
CustomEvent customEvent = new CustomEvent(this, message);
eventPublisher.publishEvent(customEvent);
}
}
@SpringBootApplication
public class AppConfig {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(AppConfig.class, args);
EventPublisher publisher = context.getBean(EventPublisher.class);
publisher.publishCustomEvent("這是自定義事件的消息");
}
}
@PreDestroy
javax.annotation.PreDestroy
介紹
@PreDestroy
與@PostConstruct
一樣,是 Java EE 中的一個註解,用於在 Spring 容器銷燬 Bean 之前執行特定的方法。這個註解通常用於釋放資源、關閉連接、清理緩存等操作。與 @PostConstruct
類似,@PreDestroy
註解的方法會在 Bean 被銷燬之前被調用。
使用場景
使用場景與 DisposableBean 類似,具體看下文。
DisposableBean
org.springframework.beans.factory.DisposableBean
介紹
這個擴展點也只有一個方法:destroy()
,其觸發時機爲當此對象銷燬、Spring 容器關閉時,會自動執行這個方法。比如說運行applicationContext.registerShutdownHook
時,就會觸發這個方法。這個擴展點基本上用不到
注意:
-
DisposableBean 是一個接口,爲 Spring bean 提供了一種釋放資源的方式 ,只有一個擴展方法 destroy();
-
實現 DisposableBean 接口,並重寫 destroy(),可以在 Spring 容器銷燬 bean 的時候獲得一次回調;
-
destroy() 的回調執行時機是 Spring 容器關閉,需要銷燬所有的 bean 時;
-
與 InitializingBean 比較類似的是,InitializingBean#afterPropertiesSet() 是在 bean 初始化的時候觸發執行,DisposableBean#destroy() 是在 bean 被銷燬的時候觸發執行
使用場景
- 釋放數據庫連接,清理臨時文件:在應用被關閉時,釋放數據庫連接以確保資源被正確地回收,刪除臨時文件以確保磁盤空間被正確釋放。
@Component
public class DatabaseConnectionManager implements DisposableBean {
@Override
public void destroy() {
System.out.println("釋放數據庫連接:關閉連接");
// 模擬關閉數據庫連接
closeConnection();
}
private void closeConnection() {
System.out.println("數據庫連接已關閉");
}
}
總結
我們從這些 spring&springboot 的擴展點當中,大致可以窺視到整個 bean 的生命週期。在業務開發或者寫中間件業務的時候,可以合理利用 spring 提供給我們的擴展點,在 spring 啓動的各個階段內做一些事情。以達到自定義初始化的目的。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/O0W6vCuqQfeAH0UB7elpeA