Spring Boot 执行 main 方法,其实就是执行 SpringApplication 的 run 方法。
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
run 方法是 SpringApplication 的静态方法,其中会生成 SpringApplication 实例对象,真正执行的是实例对象的 run 方法。SpringFactoriesLoader 加载 ApplicationContextInitializer 的过程就发生在生成 SpringApplication 实例的过程中。 类加载完毕,且生成了实例,那这些初始化器什么时候生效呢?如下是 run 方法执行流程。
常用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.properties
或bootstrap.yml
**文件中定义的。Spring Cloud提供了一个Bootstrap上下文,它可以用来加载Bootstrap配置。Bootstrap上下文会在应用程序的ApplicationContext之前加载,并且具有更高的优先级。这样做的目的是为了确保应用程序在启动时首先加载Bootstrap配置,以便在其他组件启动之前就完成配置的加载和初始化。
BootstrapConfiguration
类是Spring Cloud用来配置Bootstrap上下文的类。它提供了一些标准的配置选项,例如PropertySourceLocator
、EnvironmentPostProcessor
、**ApplicationContextInitializer
等,可以用来在Bootstrap上下文中加载和处理配置信息。如果需要自定义Bootstrap配置,可以通过继承BootstrapConfiguration
类或实现BootstrapConfigurationProvider
**接口来实现。在Spring Cloud应用程序中,Bootstrap配置的加载顺序是:首先加载**
bootstrap.properties
或bootstrap.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 定义。
主要作用和使用方式:
- 它实现了 postProcessBeanDefinitionRegistry 方法, 可以在其他 bean 定义被加载之前, 注册额外的 bean 定义。
- 实现该接口, 并在 postProcessBeanDefinitionRegistry 方法中调用 registry. registerBeanDefinition 手动注册 bean 定义。
- 将实现类注册为 Spring bean, 这样 postProcessBeanDefinitionRegistry 方法会被自动调用。
- 这就允许在不修改配置的情况下, 通过代码动态注册 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 的定义。
主要用途是:
- 修改 Bean 定义的元数据, 例如更改 Bean 的作用域、lazy-init 状态等
- 为 Bean 添加属性值、方法调用等额外逻辑
- 添加 Bean 之间的关系, 例如依赖、继承等
使用方法:
- 实现 BeanFactoryPostProcessor 接口, 并实现 postProcessBeanFactory 方法
- 在该方法中, 可以通过 BeanFactory 参数获取 Bean 定义, 然后进行修改
- 在 Spring 配置中定义这个 BeanFactoryPostProcessor 的 Bean
- 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 定义被加载之前调用。
主要作用和特点:
- 可以通过 ImportSelector 返回需要导入的配置类
ImportSelector 会返回需要导入的全类名数组, 这些类会被 Spring 加载成 Bean 定义。
ImportBeanDefinitionRegistrar 中的 registerBeanDefinitions 方法参数中可以获取这些类名, 然后可以自定义这些类的 Bean 定义。
- 可以完全自定义 Bean 定义
在 registerBeanDefinitions 方法中, 可以通过 BeanDefinitionRegistry 自行注册任何 Bean 定义, 不局限于 ImportSelector 返回的类型。
- 可以修改已注册的 Bean 定义
方法参数中的 BeanDefinitionRegistry 也可以修改已注册的 Bean 定义。
- 执行时机早于 BeanFactoryPostProcessor
ImportBeanDefinitionRegistrar 的回调方法会在 Bean 定义注册之前执行, 比 BeanFactoryPostProcessor 要早, 所以可以预处理 Bean 定义。
- 不需要实现为 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()
方法中进行了繁重的操作,可能会影响应用启动的速度。因此,建议仅在确实需要的情况下使用,并尽可能保持方法的轻量级。
评论区