写点什么

万字长文带你窥探 Spring 中所有的扩展点

作者:EquatorCoco
  • 2024-09-09
    福建
  • 本文字数:34754 字

    阅读完需:约 114 分钟

写在前面


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对它有较多的扩展实现。


使用场景


1、在应用启动时进行环境配置:可以使用 ApplicationContextInitializer 来在应用上下文初始化时进行一些环境相关的配置,例如设置系统属性、加载外部配置文件等。


public class EnvironmentInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {    @Override    public void initialize(ConfigurableApplicationContext applicationContext) {        ConfigurableEnvironment environment = applicationContext.getEnvironment();        environment.setActiveProfiles("development");        System.out.println("配置文件设置为development");    }}
复制代码


2、注册自定义的 BeanFactoryPostProcessor 或者 BeanPostProcessorApplicationContextInitializer 可以用来注册自定义的 BeanFactoryPostProcessor 或者 BeanPostProcessor,以便在 Bean 初始化之前或之后进行某些自定义处理。


public class CustomBeanFactoryPostProcessorInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {    @Override    public void initialize(ConfigurableApplicationContext applicationContext) {        applicationContext.addBeanFactoryPostProcessor(beanFactory -> {            // 添加自定义的 BeanFactoryPostProcessor            System.out.println("添加了自定义BeanFactory后处理器...");        });    }}
复制代码


3、动态地添加 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让并且它生效的方式有三种:


1、手动调用的 setXXX 方法添加


ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
// Add initializercontext.addApplicationListener(new TestApplicationContextInitializer());
// Set config locations and refresh contextcontext.setConfigLocation("classpath:applicationContext.xml");context.refresh();
// Use the context// ...
context.close();
复制代码


2、Spring 的 XML 配置文件中注册


<context:initializer class="com.seven.springsrpingbootextentions.extentions.TestApplicationContextInitializer"/>
复制代码


3、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 中让它生效的方式也有三种:


1、在启动类中用springApplication.addInitializers(new TestApplicationContextInitializer())语句加入


@SpringBootApplicationpublic class MySpringExApplication {
public static void main(String[] args) { SpringApplication application = new SpringApplication(MySpringExApplication.class); application.addInitializers(new TestApplicationContextInitializer()); // 直接在SpringApplication中添加 application.run(args); }}
复制代码


2、application 配置文件 配置 com.seven.springsrpingbootextentions.extentions.TestApplicationContextInitializer


# application.yml文件context:  initializer:    classes: com.seven.springsrpingbootextentions.extentions.TestApplicationContextInitializer
复制代码


3、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 的实例化


使用场景


1、修改 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"); }}
复制代码


2、动态注册 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; }}
复制代码


3、修改 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); }}
复制代码


4、属性占位符替换:可以使用 PropertyPlaceholderConfigurer 实现 BeanFactoryPostProcessor 接口,来替换 Bean 定义中的属性占位符。


public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
@Override protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException { super.processProperties(beanFactory, props); // 自定义属性处理逻辑 }}
复制代码


其它使用场景:


  1. 配置中心集成:当需要从外部配置中心(如 Spring Cloud Config 或 Apache Zookeeper)动态加载配置并修改 Bean 定义时,可以使用 BeanFactoryPostProcessor

  2. 多环境支持:根据不同的环境(如开发、测试、生产环境)动态修改 Bean 的定义,确保不同环境下的 Bean 配置有所不同。

  3. 动态注册 Bean:在应用运行时,根据条件动态注册或者取消 Bean,比如在某些特定条件下才需要注册某些 Bean。

  4. 复杂业务应用:有时候会需要根据复杂的业务规则动态调整 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。具体看调用顺序图


使用场景


1、修改现有的 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 { // 此方法可以留空或用于进一步处理 }}
复制代码


2、条件性地注册 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; }}
复制代码


3、扫描和注册自定义注解的 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 { // 此方法可以留空或用于进一步处理 }}
复制代码


4、比如依赖 Redis.jar,如果该依赖 jar 存在,则用redis当缓存,否则就用本地缓存。这个需求完全可以在 postProcessBeanDefinitionRegistry 中利用 Class.forName 判断依赖,存在的话则注册对应 class 到容器。


@Configurationpublic 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 { // 此方法可以留空或用于进一步处理 } }; }}
复制代码


5、Mybatis 就是用 BeanFactoryPostProcessor 去注册的 mapper

MapperScannerConfigurer 的主要功能是通过扫描指定的包路径,找到所有标注了 @Mapper 注解(或其他指定注解)的接口,并将这些接口注册为 Spring 的 BeanDefinition。这样,Spring 容器在启动时会自动创建这些 Mapper 接口的代理对象,并将其注入到需要的地方。


以下是 MapperScannerConfigurer 的核心代码片段:


// org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistrypublic 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 初始化方法(如 @PostConstructInitializingBean.afterPropertiesSet 或自定义初始化方法)调用之前执行;返回的对象将是实际注入到容器中的 Bean,如果返回 null,则该 Bean 不会被注册。可用于创建代理类


  • postProcessAfterInitialization:初始化 bean 之后,返回的对象将是实际注入到容器中的 Bean,如果返回 null,则该 Bean 不会被注册。


使用场景


1、初始化前后进行自定义逻辑:在 Bean 初始化之前或之后执行一些自定义的操作,例如设置一些属性、进行依赖注入、执行某些检查等。


@Componentpublic 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{name='" + name + "'}"; }
复制代码


2、代理对象的生成:在 postProcessAfterInitialization 方法中生成 Bean 的代理对象,用于 AOP(面向切面编程)或其他用途。


@Componentpublic 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; }}
复制代码


3、日志记录和监控:记录 Bean 的初始化过程,进行性能监控、日志记录等。


@Componentpublic 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; }}
复制代码


4、自动装配和注入:在初始化前后进行自动装配和注入,例如通过反射为某些字段注入值。


@Componentpublic 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 的生命周期中进行更精细的控制。


使用场景


1、在实例化之前替换 Bean:替换默认的 Bean 实例化过程,可能是返回一个代理对象。


@Componentpublic 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; }}
复制代码


2、控制实例化后的依赖注入过程:在实例化后但在依赖注入之前进行一些自定义逻辑。


@Componentpublic 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; }}
复制代码


3、修改属性值:在属性值设置过程中进行干预,修改或添加属性值。


@Componentpublic 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 的类的方法(注意,这里说的是两者都有的方法)。


使用场景


1、自定义构造函数选择:在实例化 Bean 时,选择特定的构造函数。


@Componentpublic 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{name='" + name + "'}"; }}
复制代码


2、解决循环依赖问题:通过提供早期 Bean 引用,解决循环依赖问题。


@Componentpublic 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; }}
复制代码


3、预测 Bean 类型:在 Bean 实例化之前,预测 Bean 的类型。


@Componentpublic 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 定义被重置时,重置自定义的元数据或状态。


使用场景


1、对合并后的 Bean 定义信息进行修改:在 Bean 实例化之前,修改其定义信息,例如添加属性值或修改构造函数参数。


@Componentpublic 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{name='" + name + "'}"; }}
复制代码


2、实现通用的自定义逻辑:在所有 Bean 实例化之前,执行一些通用的自定义逻辑。


@Componentpublic 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{name='" + name + "', commonProperty='" + commonProperty + "'}"; }}
复制代码


3、条件性地重置 Bean 定义信息:在某些条件下重置 Bean 的定义信息,使得下一次的实例化可以使用更新后的定义信息。


@Componentpublic 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{name='" + name + "'}"; }}
复制代码


BeanNameAware


org.springframework.beans.factory.BeanNameAware


介绍


这个类是 Aware 扩展的一种,触发点在 bean 的初始化之前,也就是postProcessBeforeInitialization之前,这个类的触发点方法只有一个:setBeanName

用于让 Bean 获得其在 Spring 容器中的名称。实现了 BeanNameAware 接口的 Bean 可以在初始化时获得自身的 Bean 名称,这在某些需要根据 Bean 名称进行逻辑处理的场景非常有用。


使用场景


1、记录或日志输出 Bean 名称:在某些应用场景中,开发者可能希望在 Bean 初始化时记录或输出 Bean 的名称。这对调试和日志记录非常有帮助。


@Componentpublic 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(); }}
复制代码


2、根据 Bean 名称实现条件性逻辑:有时,一个 Bean 可能需要根据其名称决定执行不同的逻辑。例如,可以在初始化过程或某些方法调用中根据 Bean 名称执行特定操作。


@Componentpublic 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(); }}
复制代码


3、动态注册多个同类型的 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 是非常有用的。


使用场景


1、动态加载类:有时候,我们可能需要在运行时动态加载类,利用 BeanClassLoaderAware 可以方便地获取到 ClassLoader 来实现这一需求。


@Componentpublic 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); } }}
@SpringBootApplicationpublic 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("不存在的类"); }}
复制代码


2、检查类的可用性:在某些情况下,我们可能需要检查某个类是否在当前的类路径中可用。利用 BeanClassLoaderAware 可以方便地实现这一需求。


@Componentpublic 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; } }}
@SpringBootApplicationpublic 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("不存在的类"); }}
复制代码


3、加载资源文件:通过 BeanClassLoaderAware 获取的 ClassLoader,我们还可以方便地加载资源文件。


@Componentpublic 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); } }}
@SpringBootApplicationpublic 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 的状态等。


使用场景


1、动态获取其他 Bean:通过实现 BeanFactoryAware 接口,一个 Bean 可以在运行时动态获取其他 Bean。这在一些需要解耦的场景下非常有用。


@Componentpublic 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); }}
@Componentpublic 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(); }}
复制代码


2、检查 Bean 的状态:通过 BeanFactoryAware,可以在运行时检查某个 Bean 是否存在或者其状态,这对一些需要动态检查 Bean 状态的场景非常有用。


@Componentpublic 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(); }}
复制代码


3、创建复杂 Bean 的初始化逻辑:在一些复杂的业务场景中,有时需要在 Bean 初始化时执行一些复杂的逻辑,例如动态创建其他 Bean 并注入到当前 Bean 中。通过 BeanFactoryAware 可以实现这一点。


@Componentpublic 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); // 在这里可以执行复杂的初始化逻辑 }}
@Componentpublic 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也实现了BeanFactoryMessageSourceApplicationEventPublisher等接口,也可以用来做相关接口的事情。


使用场景


1、动态获取其他 Bean:通过实现 ApplicationContextAware 接口,Bean 可以在运行时动态获取其他 Bean,这在一些需要解耦的场景下非常有用。


@Componentpublic 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); }}
@Componentpublic 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(); }}
复制代码


2、使用 ApplicationContext 进行事件发布:在一些场景中,Bean 可能需要发布事件。通过实现 ApplicationContextAware 接口,可以方便地获取 ApplicationContext 实例并发布事件。


@Componentpublic 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; }}
@Componentpublic 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("这是一个自定义事件消息"); }}
复制代码


3、获取环境信息:通过实现 ApplicationContextAware 接口,Bean 可以访问 ApplicationContext,并从中获取环境配置信息,例如读取配置文件中的属性值。


@Componentpublic 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 反射的方式进行调用 ;


使用场景


1、初始化资源:可以在 Bean 初始化后自动启动一些资源,如数据库连接、文件读取等。


public class NormalBeanA implements InitializingBean{    @Overrideimport org.springframework.beans.factory.InitializingBean;import org.springframework.stereotype.Component;
@Componentpublic 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(); }}
复制代码


2、设置初始值


@Componentpublic 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(); }}
复制代码


3、加载配置:可以在 Bean 初始化后加载必要的配置,如从文件或数据库中读取配置。


@Componentpublic 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();


使用场景


1、全局初始化操作:可以在所有单例 Bean 初始化后执行一些全局性的初始化操作,比如设置缓存、启动全局调度任务等。


@Componentpublic class GlobalInitializer implements SmartInitializingSingleton {
@Override public void afterSingletonsInstantiated() { // 模拟全局初始化操作 System.out.println("全局初始化操作:启动全局调度任务"); }}
复制代码


2、检查系统状态:可以用于在所有单例 Bean 初始化之后检查系统状态,确保系统运行在预期状态下。


3、加载全局配置:可以在所有单例 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 的工厂;


使用场景


1、创建复杂对象:使用 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{name='" + name + "', value=" + value + "}"; }}
@Componentpublic 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); }}
复制代码


2、动态切换实现:假设我们需要根据某些条件动态切换 Bean 的具体实现类,可以使用 FactoryBean

java


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"); }}
@Componentpublic 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(); }}
复制代码


3、延迟初始化:FactoryBean 可以用于延迟初始化某些 Bean,只有在第一次获取时才进行实例化


class LazyObject {    public LazyObject() {        System.out.println("懒对象被创建");    }
public void doSomething() { System.out.println("懒对象执行操作"); }}
@Componentpublic 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 可以应用的场景:项目启动前,热点数据的预加载、清除临时文件、读取自定义配置信息等;


使用场景


1、初始化数据:使用 CommandLineRunner 可以在应用启动后初始化一些必要的数据,例如从数据库加载某些配置或插入初始数据。


@Componentpublic class DataInitializer implements CommandLineRunner {
@Override public void run(String... args) { System.out.println("初始化数据:插入初始数据"); // 模拟插入初始数据 insertInitialData(); }
private void insertInitialData() { System.out.println("插入数据:用户表初始数据"); }}
复制代码


2、启动后执行任务:使用 CommandLineRunner 可以在应用启动后执行一些特定的任务,比如发送一个通知或启动一些背景任务。


@Componentpublic class TaskExecutor implements CommandLineRunner {
@Override public void run(String... args) { System.out.println("启动后执行任务:发送启动通知"); // 模拟发送启动通知 sendStartupNotification(); }
private void sendStartupNotification() { System.out.println("通知:应用已启动"); }}
复制代码


3、读取命令行参数:使用 CommandLineRunner 可以获取并处理命令行参数,这对于需要根据启动参数动态配置应用的场景非常有用。


@Componentpublic class CommandLineArgsProcessor implements CommandLineRunner {
@Override public void run(String... args) { System.out.println("处理命令行参数:"); for (String arg : args) { System.out.println("参数:" + arg); } }}
@SpringBootApplicationpublic 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; }}
// 监听自定义事件@Componentpublic 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()); }}
@Componentpublic 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); }}
@SpringBootApplicationpublic 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 被销毁的时候触发执行


使用场景


  • 释放数据库连接,清理临时文件:在应用被关闭时,释放数据库连接以确保资源被正确地回收,删除临时文件以确保磁盘空间被正确释放。


@Componentpublic class DatabaseConnectionManager implements DisposableBean {
@Override public void destroy() { System.out.println("释放数据库连接:关闭连接"); // 模拟关闭数据库连接 closeConnection(); }
private void closeConnection() { System.out.println("数据库连接已关闭"); }}
复制代码


总结


我们从这些 spring&springboot 的扩展点当中,大致可以窥视到整个bean的生命周期。在业务开发或者写中间件业务的时候,可以合理利用 spring 提供给我们的扩展点,在 spring 启动的各个阶段内做一些事情。以达到自定义初始化的目的。


文章转载自:Seven

原文链接:https://www.cnblogs.com/seven97-top/p/18402945

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
万字长文带你窥探Spring中所有的扩展点_Java_EquatorCoco_InfoQ写作社区