SpringBoot中的扩展点

发布于:2024-04-22 ⋅ 阅读:(284) ⋅ 点赞:(0)
ApplicationContextInitializer的initialize方法。

时机 : 所有的配置文件都已经加载,spring容器还没被刷新之前 准备阶段 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); 

它允许开发人员在Spring应用上下文(ApplicationContext)被创建和刷新之前,对上下文进行自定义的初始化设置。通过实现ApplicationContextInitializer接口,您可以执行以下类型的自定义功能:

  1. 定制ApplicationContext配置:

    • 添加或修改ApplicationContext的属性。
    • 设置环境变量(Environment)或系统属性。
    • 注册自定义的PropertySources,以便在上下文初始化时注入自定义的属性值。
  2. 注册额外的Bean Definition:

    • 在Spring IoC容器初始化之前预先注册自定义的Bean,这样在容器启动时就可用。
    • 注册自定义的BeanPostProcessor、Advisor等,用于增强或修改容器中其他Bean的行为。
  3. 定制资源加载:

    修改上下文的资源加载策略,例如添加自定义的ResourceLoader或修改其默认行为。
  4. 初始化第三方库或中间件:

    初始化与Spring容器一起使用的第三方库或中间件,例如数据库连接池、缓存服务、消息队列客户端等。
  5. 初始化应用级基础设施:

    设置应用启动时所需的基础设施,如初始化日志框架、加密服务、国际化设置等。
  6. 执行特定的初始化逻辑:

    执行一次性的初始化动作,比如配置系统层面的设定、读取外部配置文件并注入到环境中等。

示例代码如下:

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.env.ConfigurableEnvironment;

public class CustomApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 获取环境对象
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        
        // 设置或修改环境属性
        environment.getPropertySources().addFirst(new MyCustomPropertySource());

        // 注册自定义的Bean
        applicationContext.registerBean("customBean", CustomBean.class);

        // 执行其他初始化逻辑
        // ...
    }
}
BeanDefinitionRegistryPostProcessor

refresh()的 this.invokeBeanFactoryPostProcessors(beanFactory);此时此bean的定义信息 都已经加载完毕 但是还没到实例化以及初始化阶段。

主要用于在Spring IoC容器初始化过程中对Bean定义进行进一步的加工或扩展,可以拓展的功能包括但不限于:

  1. 添加自定义Bean定义:

    在容器完成所有默认的Bean定义扫描和注册之后,但在任何Bean实例化之前,可以动态地向容器中注册新的Bean定义,这对于实现动态扩展Bean的集合非常有用。
  2. 修改现有的Bean定义:

    改变Bean的属性,如修改构造函数参数、修改Bean的scope(作用域)、修改初始化方法或销毁方法等。
  3. 删除不必要的Bean定义:

    根据运行时条件或配置,可以选择性地移除已经注册的Bean定义,避免某些Bean实例化和初始化。
  4. 调整Bean之间的依赖关系:

    动态地更改Bean之间的依赖关系,例如添加额外的depends-on属性,确保Bean按期望的顺序初始化。
  5. 配置元数据处理:

    处理基于注解的Bean定义,例如解析自定义注解并据此更新Bean定义的元数据。
  6. 处理第三方库或遗留系统的Bean定义:

    当第三方库没有使用Spring的@Component注解,但又需要纳入Spring容器管理时,可以使用BeanDefinitionRegistryPostProcessor来注册这些类为Spring Bean。
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

@Configuration
public class CustomBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 注册新的Bean定义
        GenericBeanDefinition customBeanDef = new GenericBeanDefinition();
        customBeanDef.setBeanClass(CustomBean.class);
        registry.registerBeanDefinition("customBean", customBeanDef);

        // 修改已有Bean定义
        BeanDefinition existingBeanDef = registry.getBeanDefinition("someExistingBean");
        existingBeanDef.setAttribute("customAttribute", "newValue");

        // 删除Bean定义
        if (registry.containsBeanDefinition("toBeDeletedBean")) {
            registry.removeBeanDefinition("toBeDeletedBean");
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 此方法也可以进行一些工厂级别的后处理,但主要用于处理BeanFactory而非BeanDefinitionRegistry
    }
}

我们老是讲BeanDefinition,那么它是啥呢?

BeanDefinition是IoC(Inversion of Control,控制反转)容器的基本组成单元,它描述了Spring容器中Bean的元信息。每个由Spring容器管理的Bean都有一个对应的BeanDefinition对象,它包含了Bean的所有必要配置信息,如:

  1. Bean 类型(Class):定义了Bean的具体实现类。
  2. 作用域(Scope):定义了Bean的生命周期模式,如singleton(单例)或prototype(原型)。
  3. 构造器参数和属性值:Bean依赖的其他Bean,或者是直接的属性值。
  4. 初始化方法和销毁方法:定义了Bean实例化后要调用的初始化方法和容器关闭前要调用的销毁方法。
  5. 是否懒加载:指示Bean是否应在首次请求时才创建实例。
  6. 自定义属性:其他用户自定义的属性信息。

bean是怎么变成beandefinition的呢?Spring容器在启动和初始化过程中,会将各种来源的Bean配置信息转换成BeanDefinition对象,主要包括以下几个步骤:

  1. XML配置文件:

    当使用XML配置方式时,Spring容器读取XML配置文件,通过BeanDefinitionParserDelegate等组件解析XML标签,将标签内的配置信息转换成BeanDefinition对象。
  2. 注解驱动配置:

    当使用注解如@Component、@Service、@Repository、@Controller等进行配置时,Spring通过组件扫描(@ComponentScan)自动检测到带有这些注解的类,并使用ClassPathBeanDefinitionScanner或其他类似组件将类转换成对应的BeanDefinition。
  3. Java配置类:

    在Java配置类(如带有@Configuration注解的类)中,通过@Bean注解的方法声明的Bean,Spring会自动处理这些方法并将方法体中的逻辑转换成BeanDefinition。Spring Boot中ConfigurationClassPostProcessor会处理这些Java配置类。
  4. 手动注册:

    在代码中手动创建GenericBeanDefinition等BeanDefinition子类实例,填充相关信息后,通过DefaultListableBeanFactory或其他BeanDefinitionRegistry接口的实现注册到Spring容器。
BeanFactoryPostProcessor

允许开发者在Bean实例化之前对Spring IoC容器中的Bean定义进行修改和扩展。通过实现BeanFactoryPostProcessor接口,您可以拓展以下功能:

  1. 修改Bean属性:

    动态修改Bean定义中的属性值,例如修改构造器参数值、属性值、初始化方法、销毁方法等。
  2. 自定义Bean作用域:

    更改Bean的作用域(Scope),如将Singleton Bean改为Prototype Bean,或者自定义作用域。
  3. 注入额外的依赖:

    动态地向Bean定义中注入额外的依赖,而不必在代码中显式声明。
  4. 配置属性替换:

    能够根据运行时环境或配置文件动态替换Bean定义中的占位符属性值。
  5. 实现Bean的动态代理:

    可以在Bean实例化前为其生成代理类,添加额外的横切逻辑,如AOP(面向切面编程)的增强功能。
  6. 处理遗留系统的Bean定义:

    对于一些不遵循Spring规范的遗留系统,可以通过BeanFactoryPostProcessor将遗留系统中的类注册为Spring Bean,并进行必要的配置。
  7. 整合第三方库配置:

    在Bean实例化前,整合第三方库的相关配置,将其无缝融入Spring IoC容器的管理。

示例代码:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beanNames = beanFactory.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
            // 示例:修改某Bean定义的属性值
            if ("someBean".equals(beanName)) {
                bd.getPropertyValues().addPropertyValue("propertyName", "newPropertyValue");
            }
        }
    }
}

那么BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor看起来好像是,有啥区别呢?BeanDefinitionRegistryPostProcessor 它继承了 BeanFactoryPostProcessor 接口。

BeanFactoryPostProcessor:

  • 主要功能:允许开发人员在 Spring 容器完成所有标准 Bean 定义的读取之后但在任何 Bean 实例化之前修改这些 Bean 定义。
  • 改动范围:可以修改现有 Bean 定义的各种属性,包括但不限于构造函数参数、属性值、依赖关系等。
  • 应用场景:例如,可以用来替代配置文件中的占位符属性值,或者进行全局性的属性设置。

BeanDefinitionRegistryPostProcessor:

  • 扩展功能:除了具有 BeanFactoryPostProcessor 的能力之外,还能够注册新的 Bean 定义。
  • 特殊权限:它可以直接操作 BeanDefinitionRegistry,这意味着可以在初始化过程的更早阶段添加或删除 Bean 定义。
  • 应用场景:当需要动态添加、移除或修改整个 Bean 定义集合时特别有用,比如在扫描特定包下带有自定义注解的类并将其自动注册为 Spring Bean 的场景。

总结来说,两者的主要区别在于:

  • BeanFactoryPostProcessor 更专注于修改已有的 Bean 定义属性。
  • BeanDefinitionRegistryPostProcessor 不仅能修改,还能在 Spring 容器构建过程中增加新的 Bean 定义,提供了更高的定制化程度。

执行顺序方面,BeanDefinitionRegistryPostProcessor 先于 BeanFactoryPostProcessor 执行。这样设计的原因在于,确保在 Bean 定义的最终集合形成后,再给所有 Bean 定义提供一个统一修改的机会。最后,所有经过这些后置处理器处理过的 Bean 定义才会被用来创建实际的 Bean 实例。

BeanPostProcessor

它允许开发者在 Spring 容器创建 Bean 实例后,但在 Bean 初始化前后对其进行自定义处理。通过实现 BeanPostProcessor 接口,可以拓展以下功能:

  1. 字段注入或方法调用:

    在 Bean 初始化之前或之后添加额外的属性注入,或者调用自定义方法。
  2. AOP 切面增强:

    实现面向切面编程(AOP),在 Bean 创建后,对其进行代理,添加横切关注点,如事务管理、日志记录、权限控制等。
  3. 对象包装:

    将 Bean 包装成代理对象,以实现在调用 Bean 方法时附加额外的逻辑。
  4. 懒加载优化:

    实现自定义的懒加载策略,延迟某些 Bean 的初始化时间点。
  5. 属性修改:

    在 Bean 初始化前后,修改 Bean 实例的属性值。
  6. 初始化前后自定义逻辑:

    在 Bean 初始化前后执行自定义的初始化或清理逻辑。
  7. 自定义依赖注入:

    根据自定义逻辑动态注入 Bean 的依赖,而非仅依赖于 Spring 的默认注入机制。

具体实现这两个方法:

  • Object postProcessBeforeInitialization(Object bean, String beanName): 在 Bean 初始化(调用 InitializingBean 接口的 afterPropertiesSet() 方法或 @PostConstruct 注解的方法)之前调用。

  • Object postProcessAfterInitialization(Object bean, String beanName): 在 Bean 初始化之后调用,但在 Bean 准备好被应用上下文使用之前调用。

如下例子将展示如何在 Spring 容器中创建 Bean 实例之后对其内容进行自定义操作。这里我们将创建一个简单的后处理器,它会打印 Bean 的名称和类名,并对具有特定注解的 Bean 进行额外处理。

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.stream.Collectors;

@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' is being created. Bean class: " + bean.getClass().getName());
        
        // 检查Bean是否带有特定注解,如果有,则进行特殊处理
        if (bean.getClass().isAnnotationPresent(MySpecialAnnotation.class)) {
            // 假设MySpecialAnnotation注解存在并有一些自定义逻辑
            processSpecialBean(bean);
        }

        return bean; // 返回未经修改的原始Bean对象,也可以返回一个包装过的Bean
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' has been initialized.");
        return bean; // 返回未经修改的原始Bean对象,同样可以返回一个包装过的Bean
    }

    private void processSpecialBean(Object bean) {
        // 假设这里的逻辑是对标注了MySpecialAnnotation的Bean的字段进行赋值或其它操作
        Class<?> clazz = bean.getClass();
        Arrays.stream(clazz.getDeclaredFields())
              .filter(field -> field.isAnnotationPresent(MySpecialAnnotation.class))
              .forEach(field -> {
                  try {
                      field.setAccessible(true);
                      // 这里进行自定义的字段赋值或其他操作
                      // ...
                  } catch (IllegalAccessException e) {
                      throw new RuntimeException("Error processing annotated field in bean", e);
                  }
              });
    }

    // 假设的自定义注解
    @interface MySpecialAnnotation {
        // 注解的属性...
    }
}
InitializingBean

允许我们在 Bean 实例化后和依赖注入完成后,被其他bean依赖或者执行前执行一些自定义的初始化逻辑。通过实现 InitializingBean 接口,可以拓展以下功能:

  1. 自定义初始化操作:

    执行额外的数据校验、初始化状态设置、与其他Bean交互或建立内部依赖关系等操作。
  2. 执行一次性初始化任务:

    在Bean准备就绪投入服务之前,执行必需的一次性初始化任务,如打开数据库连接、初始化缓存、设置定时任务等。
  3. 注入后处理:

    当依赖注入完成后,可以基于注入的属性执行一些初始化逻辑。

具体执行顺序如下:

  1. Spring 容器创建 Bean 实例。
  2. 容器对 Bean 进行依赖注入(DI),也就是填充所有通过 Setter 方法、构造函数或其他注解标记的属性。
  3. 如果 Bean 实现了 InitializingBean 接口,Spring 容器接下来会调用 afterPropertiesSet() 方法。
  4. 如果 Bean 上使用了 @PostConstruct 注解的方法,这些方法将在 InitializingBean.afterPropertiesSet() 之后执行。
  5. 如果 Bean 在 XML 配置中或通过 @Configuration 类定义了一个 init-method,那么这个 init-method 方法也会在这之后执行。

下面是一个简单的 InitializingBean 接口实现的例子:

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class MyComponent implements InitializingBean {

    private String property1;
    private int property2;

    // 通过@Autowired注解或者其他方式注入属性
    public void setProperty1(String property1) {
        this.property1 = property1;
    }

    public void setProperty2(int property2) {
        this.property2 = property2;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 在所有属性注入完成后,执行自定义的初始化逻辑
        System.out.println("MyComponent has been instantiated with property1=" + property1 + " and property2=" + property2);

        // 模拟一个初始化操作,比如初始化数据库连接
        // ...

        // 或者进行数据验证
        if (property1 == null || property1.isEmpty()) {
            throw new IllegalArgumentException("Property1 must not be empty!");
        }
    }
}
@PostConstruct , @PreDestroy

@PostConstruct:

  • 该注解用于标记在一个无参的实例方法上,表示在依赖注入完成后,但在 Bean 正式投入使用(即任何业务方法被调用前)时执行的方法。
  • 这个注解的方法通常用于初始化 Bean 的内部状态,如数据库连接、初始化缓存、加载配置等。
  • 示例:
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;

@Component
public class MyComponent {

    @PostConstruct
    public void init() {
        System.out.println("Bean is being initialized...");
        // 执行初始化逻辑
    }

    // ...其他方法
}

@PreDestroy:

  • 该注解用于标记在一个无参的实例方法上,表示在 Bean 生命周期结束时,即将从 Spring 容器中移除之前执行的方法。
  • 这个注解的方法通常用于资源清理,如关闭数据库连接、清除缓存、保存状态、发送关闭通知等。
  • 示例:
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class MyComponent {

    // ...其他方法

    @PreDestroy
    public void cleanup() {
        System.out.println("Bean is about to be destroyed...");
        // 执行资源清理逻辑
    }
}

注意:在 Spring 中,@PostConstruct 和 @PreDestroy 注解的方法的执行顺序优先级高于 InitializingBean 和 DisposableBean 接口中的 afterPropertiesSet() 和 destroy() 方法,以及在 XML 配置中定义的 init-method 和 destroy-method。

BeanNameAware

BeanNameAware 是 Spring Framework 提供的一个接口,允许 Bean 实例了解自己在 Spring 容器中的 bean 名称。当一个类实现了 BeanNameAware 接口后,Spring 容器在实例化该 Bean 并注入其属性后,会调用 setBeanName() 方法,传递给 Bean 其在 Spring 配置文件中定义的 bean 名称。

示例:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;

public class MyBean implements BeanNameAware {

    private String myBeanName;

    @Override
    public void setBeanName(String name) {
        this.myBeanName = name;
        System.out.println("Bean Name is: " + name);
    }

    // ...其他方法
}

在上面的例子中,当 Spring 容器创建 MyBean 实例时,会自动调用 setBeanName() 方法,将当前 Bean 的名字传递给 myBeanName 字段。这个特性在一些需要知道自身 Bean 名称以进行特定逻辑处理的场景中会很有用。

InstantiationAwareBeanPostProcessor

它扩展了 BeanPostProcessor 接口,提供了在 Bean 实例化阶段进行介入和定制的能力。通过实现 InstantiationAwareBeanPostProcessor 接口,除非有特殊情况,否则Spring容器中大部分由Spring管理创建的Bean在实例化过程中都会受到InstantiationAwareBeanPostProcessor的监测和处理,可以实现以下功能:

  1. 自定义实例化过程:

    通过重写 postProcessBeforeInstantiation(Class<?> beanClass, String beanName) 方法,在 Spring 创建 Bean 实例之前有机会自定义实例化过程,甚至可以返回一个自定义的 Bean 实例,从而替代 Spring 默认的实例化过程。
  2. 修改初始化后的 Bean 实例:

    同样作为 BeanPostProcessor 的一部分,可以重写 postProcessBeforeInitialization(Object bean, String beanName) 和 postProcessAfterInitialization(Object bean, String beanName) 方法,在 Bean 初始化前后进行自定义处理。
  3. 决定是否提前暴露早期引用:

    通过实现 getEarlyBeanReference(Object bean, String beanName) 方法,可以在 Bean 初始化之前决定是否提前暴露该 Bean 的早期引用给其他 Bean 使用。
  4. AOP(面向切面编程)支持:

    在某些情况下,例如在实现 AOP 代理时,需要在 Bean 实例化阶段就能创建代理对象,而不是等待常规的 Bean 初始化阶段。InstantiationAwareBeanPostProcessor 为此类场景提供了支持。

示例:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;

public class CustomInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (YourCustomCondition.check(beanClass)) {
            return new YourCustomBeanImplementation();
        }
        return null; // 返回null表示仍然使用Spring默认的实例化方式
    }

    // ...其他BeanPostProcessor方法的实现
}

InstantiationAwareBeanPostProcessor 相对于 BeanPostProcessor 提供了更早的介入时机,允许在 Bean 实例化阶段就进行自定义处理。通过实现 InstantiationAwareBeanPostProcessor,可以实现诸如自定义实例化过程、提前修改属性值等高级定制需求。

它们在作用时机上有微妙的区别:

  1. BeanPostProcessor:

    • BeanPostProcessor 接口提供了在 Spring 容器创建 Bean 实例并完成依赖注入之后,但在调用初始化方法(如 @PostConstruct 注解的方法或者 InitializingBean 接口的 afterPropertiesSet() 方法)之前和之后执行自定义逻辑的机会。
    • 有两个关键方法:
      • postProcessBeforeInitialization(Object bean, String beanName):在 Bean 初始化前调用。
      • postProcessAfterInitialization(Object bean, String beanName):在 Bean 初始化后调用。
  2. InstantiationAwareBeanPostProcessor:

    • InstantiationAwareBeanPostProcessor 是 BeanPostProcessor 的一个子接口,它在 Bean 实例化阶段提供了更多的介入点。
    • 在 Bean 实例化前后,除了拥有 BeanPostProcessor 的全部功能外,还提供了如下方法:
      • postProcessBeforeInstantiation(Class<?> beanClass, String beanName):在 Spring 实际创建 Bean 实例之前调用。如果这个方法返回非空对象,那么 Spring 将不再使用默认的实例化策略,而是使用返回的对象作为 Bean 实例。
      • postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName):在实例化后、初始化前修改 Bean 属性值的阶段调用,允许在注入属性值之后,初始化方法调用之前修改属性值。


微信公众号

今日签到

点亮在社区的每一天
去签到