XiaoLin's Blog

Xiao Lin

Spring Boot 常用拓展点

31
2024-02-04

Spring Boot 执行 main 方法,其实就是执行 SpringApplication 的 run 方法。

public static void main(String[] args) {
  SpringApplication.run(DemoApplication.class, args);
}

run 方法是 SpringApplication 的静态方法,其中会生成 SpringApplication 实例对象,真正执行的是实例对象的 run 方法。SpringFactoriesLoader 加载 ApplicationContextInitializer 的过程就发生在生成 SpringApplication 实例的过程中。 类加载完毕,且生成了实例,那这些初始化器什么时候生效呢?如下是 run 方法执行流程。

Untitled 14.png

常用SPI拓展点

在 Spring Boot 中,有很多常用的 SPI 拓展点,包括:

ApplicationContextInitializer - 刷新上下文之前

  • 使用场景 在SpringCloudConfig中,需要在SDK中对配置文件中的加密串”{gs}”开头的字符串解密,该操作需要在其他框架使用配置参数前修改,ApplicationContextInitializer属于刷新上下文之前的操作,刚好满足我们的需求

使用

  • 实现类
package com.gs.encrypt;

import java.util.Collections;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.*;
import org.springframework.util.StringUtils;

/**
 *
 * @author huangmuhong
 * @version 1.0.0
 * @date 2023/2/23
 *
 **/
public class SMEnvironmentDecryptApplicationContextInitializer
    implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    private static final String PREFIX = "{gs}";

    private final SMEncrypt smEncrypt;

    public SMEnvironmentDecryptApplicationContextInitializer(SMEncrypt smEncrypt) {
        this.smEncrypt = smEncrypt;
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        final ConfigurableEnvironment environment = applicationContext.getEnvironment();
        MutablePropertySources propertySources = environment.getPropertySources();
        for (PropertySource<?> propertySource : propertySources) {
            if (propertySource instanceof EnumerablePropertySource) {
                EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource<?>)propertySource;
                String[] propertyNames = enumerablePropertySource.getPropertyNames();
                for (String propertyName : propertyNames) {
                    String value = environment.getProperty(propertyName);
                    if (value != null && StringUtils.startsWithIgnoreCase(value, PREFIX)) {
                        String decryptedValue = smEncrypt.decrypt(value.substring(PREFIX.length()));
                        environment.getPropertySources().addLast(new MapPropertySource(propertyName,
                            Collections.singletonMap(propertyName, decryptedValue)));
                    }
                }
            }
        }
    }

    @Override
    public int getOrder() {
        return -10;
    }

}
  • 注册到spring容器之中,通过spring.factories配置
org.springframework.context.ApplicationContextInitializer=\\
  com.gs.encrypt.SMEnvironmentDecryptApplicationContextInitializer

这样启动spring容器后,每次刷新上下文之前该SPI都会被触发,执行初始化方法内的代码

问题

通过上述方法配置后,该bean内无法使用容器内的对象,我们可以通过使用org.springframework.cloud.bootstrap.BootstrapConfiguration

注入一个配置类,在该配置类中完成我们ApplicationContextInitializer类需要的bean创建,并在创建ApplicationContextInitializer对象的时候将需要的bean注入,这样子就能达到使用容器内bean的效果,这里我们使用bootstrap配置的方式实现

**org.springframework.cloud.bootstrap.BootstrapConfiguration**是Spring Cloud中用来实现Bootstrap配置的类。

Bootstrap配置是指应用程序在启动时需要加载的配置。它通常用来配置应用程序需要连接的配置中心、密钥库、信任库、日志级别等全局配置信息。在Spring Boot应用程序中,Bootstrap配置通常是在**bootstrap.propertiesbootstrap.yml**文件中定义的。

Spring Cloud提供了一个Bootstrap上下文,它可以用来加载Bootstrap配置。Bootstrap上下文会在应用程序的ApplicationContext之前加载,并且具有更高的优先级。这样做的目的是为了确保应用程序在启动时首先加载Bootstrap配置,以便在其他组件启动之前就完成配置的加载和初始化。

BootstrapConfiguration类是Spring Cloud用来配置Bootstrap上下文的类。它提供了一些标准的配置选项,例如PropertySourceLocatorEnvironmentPostProcessor、**ApplicationContextInitializer等,可以用来在Bootstrap上下文中加载和处理配置信息。如果需要自定义Bootstrap配置,可以通过继承BootstrapConfiguration类或实现BootstrapConfigurationProvider**接口来实现。

在Spring Cloud应用程序中,Bootstrap配置的加载顺序是:首先加载**bootstrap.propertiesbootstrap.yml文件中的配置,然后加载org.springframework.cloud.bootstrap.BootstrapConfiguration**类中定义的配置。这样做的好处是,可以在Bootstrap配置中定义一些全局配置,然后在ApplicationContext启动之前就将其加载和初始化,以便在整个应用程序中都可以使用。

  • bootstrap配置类
package com.gs.encrypt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.client.RestTemplate;

/**
 *
 * 加密引导配置,需注册成bootstrap配置
 *
 * @author huangmuhong
 * @version 1.0.0
 * @date 2023/02/24
 *
 **/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.cloud.config.custom", name = "enabled", havingValue = "true",
    matchIfMissing = true)
public class EncryptConfiguration {

    @Bean
    public SMEncrypt smEncrypt(@Autowired(required = false) RestTemplate restTemplate,
        ConfigClientProperties properties, Environment environment) {
        return new SMEncrypt(restTemplate, properties, environment);
    }

    @Bean
    public SMEnvironmentDecryptApplicationContextInitializer
        smEnvironmentDecryptApplicationListener(SMEncrypt smEncrypt) {
        return new SMEnvironmentDecryptApplicationContextInitializer(smEncrypt);
    }

}
  • spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=\\
  com.gs.encrypt.EncryptConfiguration

这样配置后,我们的自定义上下文对象就可以使用我们注册的bean了!

ApplicationListener - 刷新上下文之后执行

在 Spring 应用程序中,ApplicationListener接口是一个重要的扩展点,它可以监听应用程序中发生的各种事件,并在事件发生时执行相应的逻辑。例如,可以使ApplicationListener 监听应用程序启动事件,在应用程序启动后执行一些初始化操作,也可以监听数据库连接池关闭事件,在数据库连接池关闭前执行一些清理操作。

**ApplicationListener接口定义了一个方法 onApplicationEvent(ApplicationEvent event),该方法在监听到事件时调用。ApplicationEvent**是所有 Spring 应用程序事件的基类,例如 **ContextRefreshedEvent表示应用程序上下文刷新事件,ContextStartedEvent**表示应用程序上下文启动事件等。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    void onApplicationEvent(E event);
}

监听上下文刷新事件

package com.gs.config;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

/**
 *
 * @author huangmuhong
 * @version 1.0.0
 * @date 2023/2/26
 *
 **/
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext().getParent() == null) {
            System.out.println("应用程序上下文刷新完成...");
        }
    }
}

上述代码创建了一个实现了 ApplicationListener 接口的 Spring Bean,并重写了 onApplicationEvent() 方法,当监听到应用程序上下文刷新事件时,判断父上下文是否为空,如果为空则表示应用程序上下文已经刷新完成,输出日志。

需要注意的是,ApplicationListener 接口定义了一个泛型类型参数 E extends ApplicationEvent,该参数表示监听的事件类型。如果要监听多个事件类型,可以创建多个实现了 ApplicationListener 接口的 Spring Bean,或者使用 SmartApplicationListener 接口。

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent> {

    boolean supportsEventType(Class<? extends ApplicationEvent> eventType);

    boolean supportsSourceType(Class<?> sourceType);

    default int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

SmartApplicationListener 接口定义了两个方法:supportsEventType(Class<? extends ApplicationEvent> eventType)supportsSourceType(Class<?> sourceType),用于判断是否支持监听的事件类型和事件源类型。getOrder() 方法用于设置监听器的顺序,返回值越小,执行顺序越靠前。

例如,监听多个事件类型:

@Component
public class MyApplicationListener implements SmartApplicationListener {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ContextRefreshedEvent) {
            System.out.println("应用程序上下文刷新完成...");
        } else if (event instanceof ContextClosedEvent) {
            System.out.println("应用程序上下文关闭...");
        }
    }

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return eventType == ContextRefreshedEvent.class || eventType == ContextClosedEvent.class;
    }

    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return true;
    }

    @Override
    public int getOrder() {
        return Ordered.HIG
		}
}

ImportBeanDefinitionRegistrar - 动态注入 Bean 定义的接口

在 Spring 中, ImportBeanDefinitionRegistrar 是一种用于动态注入 Bean 定义的接口。它可以实现按需注入 Bean, 而不需要使用 XML 配置。

ImportBeanDefinitionRegistrar 接口只包含一个方法:


public interface ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

使用步骤:

实现 ImportBeanDefinitionRegistrar 接口, 并实现 registerBeanDefinitions 方法
在 registerBeanDefinitions 中, 可以通过 AnnotationMetadata 参数获取注解信息
通过 BeanDefinitionRegistry 参数手动注册 BeanDefinition
在配置类上使用 @Import 注解导入实现了该接口的类
Spring 会自动调用 registerBeanDefinitions 方法注入 Bean
这样就可以完全通过 Java 配置方式, 动态注入任意 Bean, 非常灵活。

主要优点是可以将 Bean 注入逻辑封装在实现类中, 从而实现解耦合, 以及更细粒度的按需注入 Bean。

BeanDefinitionRegistryPostProcessor - 注册额外的 bean 定义

在 Spring 中, BeanDefinitionRegistryPostProcessor 是 BeanFactoryPostProcessor 的子接口, 它可以用于注册额外的 bean 定义。

主要作用和使用方式:

  1. 它实现了 postProcessBeanDefinitionRegistry 方法, 可以在其他 bean 定义被加载之前, 注册额外的 bean 定义。
  2. 实现该接口, 并在 postProcessBeanDefinitionRegistry 方法中调用 registry. registerBeanDefinition 手动注册 bean 定义。
  3. 将实现类注册为 Spring bean, 这样 postProcessBeanDefinitionRegistry 方法会被自动调用。
  4. 这就允许在不修改配置的情况下, 通过代码动态注册 bean。

示例:

@Component
public class MyBeanRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(MyBean.class);
        registry.registerBeanDefinition("myBean", beanDefinition);
    }

}

这就注册了一个 myBean 的 bean 定义。

主要优点是可以通过代码精确控制 bean 定义的注册, 扩展点更加灵活, 可以在 Spring 底层注册 bean。

BeanFactoryPostProcessor - Bean 创建之前, 修改 Bean 的定义

在 Spring 中, BeanFactoryPostProcessor 是一个重要的扩展点, 它允许我们在 Bean 创建之前, 修改 Bean 的定义。

主要用途是:

  1. 修改 Bean 定义的元数据, 例如更改 Bean 的作用域、lazy-init 状态等
  2. 为 Bean 添加属性值、方法调用等额外逻辑
  3. 添加 Bean 之间的关系, 例如依赖、继承等

使用方法:

  1. 实现 BeanFactoryPostProcessor 接口, 并实现 postProcessBeanFactory 方法
  2. 在该方法中, 可以通过 BeanFactory 参数获取 Bean 定义, 然后进行修改
  3. 在 Spring 配置中定义这个 BeanFactoryPostProcessor 的 Bean
  4. Spring 在初始化时会自动调用该方法, 来修改 Bean 定义

例如:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 操作 Bean 定义
    }

}

总之, BeanFactoryPostProcessor 提供了一个非常灵活的扩展点, 可以实现自定义的 Bean 定义修改逻辑。

ImportBeanDefinitionRegistrar - 自定义修改 import 中带来的 Bean 定义

在 Spring 中, ImportBeanDefinitionRegistrar 是一种特殊的 Bean 定义注册 callback 接口, 它允许我们自定义修改 import 中带来的 Bean 定义。

ImportBeanDefinitionRegistrar 接口中只包含一个 registerBeanDefinitions 方法, 它会在配置类被处理之后、Bean 定义被加载之前调用。

主要作用和特点:

  1. 可以通过 ImportSelector 返回需要导入的配置类

ImportSelector 会返回需要导入的全类名数组, 这些类会被 Spring 加载成 Bean 定义。

ImportBeanDefinitionRegistrar 中的 registerBeanDefinitions 方法参数中可以获取这些类名, 然后可以自定义这些类的 Bean 定义。

  1. 可以完全自定义 Bean 定义

在 registerBeanDefinitions 方法中, 可以通过 BeanDefinitionRegistry 自行注册任何 Bean 定义, 不局限于 ImportSelector 返回的类型。

  1. 可以修改已注册的 Bean 定义

方法参数中的 BeanDefinitionRegistry 也可以修改已注册的 Bean 定义。

  1. 执行时机早于 BeanFactoryPostProcessor

ImportBeanDefinitionRegistrar 的回调方法会在 Bean 定义注册之前执行, 比 BeanFactoryPostProcessor 要早, 所以可以预处理 Bean 定义。

  1. 不需要实现为 Spring 组件

ImportBeanDefinitionRegistrar 仅为一种接口, 我们可以通过匿名内部类实现而无需标注 Spring 组件。

综上, ImportBeanDefinitionRegistrar 是一个强大的扩展点, 通过实现这个接口, 我们可以主动介入 Bean 定义的注册并自定义 Bean 定义, 这为扩展 Spring 提供了更多可能性。

FactoryBean - 自定义目标类型的 bean 创建过程

对 Spring 的 FactoryBean 进行扩展, 可以实现自定义的 bean 创建逻辑。

FactoryBean 是一个接口, 它有三个方法:

  • getObject (): 返回由 FactoryBean 创建的 bean 实例。
  • getObjectType (): 返回 FactoryBean 创建的 bean 类型。
  • isSingleton (): 返回创建的 bean 是否为单例。

要实现自定义的 FactoryBean, 需要实现这个接口, 并在 getObject ()方法中编写自定义的 bean 创建逻辑。

例如, 可以这样实现一个自定义的 FactoryBean:

public class CustomFactoryBean implements FactoryBean<CustomBean> {

  @Override
  public CustomBean getObject() throws Exception {
    // 自定义逻辑创建CustomBean
    return new CustomBean(); 
  }

  @Override
  public Class<CustomBean> getObjectType() {
    return CustomBean.class;
  }

  @Override
  public boolean isSingleton() { 
    return true;
  }

}

这样在 Spring 容器中定义 CustomFactoryBean, 就可以通过这个 FactoryBean 来创建 CustomBean 实例。

Spring 在遇到 FactoryBean 时, 会调用其 getObject ()方法返回对象, 所以可以通过 FactoryBean 自定义复杂的 bean 创建逻辑。这为 Spring 提供了可扩展的 bean 创建方式。

InitializingBean - bean完成初始化之后

SpringBoot拓展点InitializingBean介绍

SpringBoot是一个非常流行的Java框架,用于快速创建微服务。它提供了大量的拓展点,允许开发者自定义和修改框架的默认行为。InitializingBean 是其中一个重要的拓展点。

InitializingBean 是Spring框架中的一个接口,当一个Bean实现了这个接口后,Spring容器在设置了所有bean属性之后,会自动调用该Bean的afterPropertiesSet方法。这个特性非常有用,因为它允许开发者在Bean的属性被初始化之后,但在Bean被使用之前,执行自定义的初始化逻辑或后处理。

使用InitializingBean非常简单,只需要实现这个接口,并覆写afterPropertiesSet方法。例如:

import org.springframework.beans.factory.InitializingBean;

public class MyBean implements InitializingBean {
    // bean properties
    private String property;

    @Override
    public void afterPropertiesSet() throws Exception {
        // initialization logic here
        System.out.println("Initializing bean with property: " + property);
    }

    // standard getters and setters
    public void setProperty(String property) {
        this.property = property;
    }
}

在上面的例子中,MyBean类实现了InitializingBean接口,因此Spring会在所有的属性(比如property)都被设置后,自动调用afterPropertiesSet方法。

然而,需要注意的是,虽然使用InitializingBean接口可以方便地实现初始化逻辑,但这并不是执行初始化的唯一方法。Spring还提供了其他方式,例如使用@PostConstruct注解或者定义一个带有@Bean注解的方法,并在该方法内执行初始化逻辑。与InitializingBean相比,这些方法通常被认为更优雅,因为它们不依赖于Spring特定的接口,因此更符合Java的一般规范。

总而言之,InitializingBean提供了一个在所有必需属性都被设置后初始化Bean的方便方式,但开发者应该考虑到其他更现代的替代方案。

SmartInitializingSingleton - 单例bean初始化完成后

SpringBoot中的SmartInitializingSingleton是Spring框架提供的一个接口,用于在所有单例bean初始化完成之后,执行某些特定的操作。这个接口定义了一个方法afterSingletonsInstantiated(),当Spring容器中的所有单例bean都初始化完成后,会回调实现了SmartInitializingSingleton接口的bean的这个方法。

在SpringBoot或Spring应用程序启动过程中,通常会有很多bean被初始化。有时候,我们需要在所有bean都初始化完毕后执行一些操作,比如执行一些后处理逻辑、校验数据、预加载数据到缓存等。SmartInitializingSingleton正是为了满足这类需求而设计的。

使用场景

  • 数据预加载:应用启动后,预先将一些数据加载到内存或缓存中。
  • 后处理逻辑:对Spring容器管理的所有bean进行某些统一的后处理操作。
  • 校验逻辑:校验bean的状态,确保应用启动前的数据完整性或环境准备情况。

如何使用

要使用SmartInitializingSingleton,你需要创建一个bean,并实现这个接口。然后,在afterSingletonsInstantiated()方法中编写你的自定义逻辑。Spring容器在完成所有单例bean的初始化之后,会自动调用这个方法。

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

@Component
public class MyDataInitializer implements SmartInitializingSingleton {

    @Override
    public void afterSingletonsInstantiated() {
        // 这里可以执行数据预加载或其他后初始化逻辑
    }
}

优点

  • 简洁性:提供了一种简洁的方式来执行所有bean初始化完成后的操作。
  • 灵活性:可以根据需要,在任何需要的bean上实现这个接口,进行特定的后初始化操作。
  • 集成性:与Spring生态系统无缝集成,无需额外的配置或管理工作。

注意事项

尽管SmartInitializingSingleton提供了强大的功能,但它也应该谨慎使用。因为它在所有单例bean初始化之后执行,所以如果在afterSingletonsInstantiated()方法中进行了繁重的操作,可能会影响应用启动的速度。因此,建议仅在确实需要的情况下使用,并尽可能保持方法的轻量级。