SpringBoot动态注册与更新ICO中的Bean

SpringBoot动态注册与更新ICO中的Bean

Spring Framework是Java生态系统中最受欢迎的开源框架之一,用于构建企业级应用程序。其中一个强大的功能是Spring容器可以管理Java Bean的生命周期,但有时候需要在运行时动态注册和更新Bean,本文将介绍如何实现这一功能。

背景

在某些情况下,我们可能需要在应用程序运行时动态添加或更新Spring Bean,比如,有时候我们的某些第三方配置信息存储与数据库中,而为了保证某一个服务的单例性质,不能每次都去动态的构建一个服务对象,此时就形成了“需要注册为Bean并且需要支持动态更新Bean”的需求。
这可以用于插件系统、模块化应用程序或需要在不重启应用的情况下更新业务规则的场景。

实现

功能实现依赖于Spring提供的ApplicationContextAware接口,基于它可以实现一个Spring上下文,Spring上下文经常在我们需要在非Bean的类中获取Spring Bean的时候用到。

Spring上下文?

构建一个类SpringContext并实现Spring提供的ApplicationContextAware接口,并重写set ApplicationContext方法,可以获取到Spring的上下文对象ApplicationContext

1
2
3
4
5
6
7
8
9
10
@Override
public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
SpringContext.context = applicationContext;

if (!(applicationContext instanceof ConfigurableApplicationContext)) {
throw new RuntimeException("applicationContext is not ConfigurableApplicationContext, can not register singleton bean");
}

SpringContext.configurableContext = (ConfigurableApplicationContext) applicationContext;
}

利用这个方法,我们可以实现一个上下文工具类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.*;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;


/**
* @author JanYork
* @version 1.0.0
* @date 2023/10/20
* @description Spring上下文
* @since 1.0.0
*/
@Component
public class SpringContext implements ApplicationContextAware, EnvironmentAware {
@Getter
private volatile static ApplicationContext context;
private volatile static Environment environment;
private volatile static ConfigurableApplicationContext configurableContext;

@Override
public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
SpringContext.context = applicationContext;

if (!(applicationContext instanceof ConfigurableApplicationContext)) {
throw new RuntimeException("applicationContext is not ConfigurableApplicationContext, can not register singleton bean");
}

SpringContext.configurableContext = (ConfigurableApplicationContext) applicationContext;
}

@Override
public void setEnvironment(@NotNull Environment environment) {
SpringContext.environment = environment;
}

/**
* 获取bean
*
* @param bean bean的class
* @param <T> bean的类型
* @return bean
*/
public static <T> T getBean(Class<T> bean) {
return context.getBean(bean);
}

public static <T> T getBean(String beanName, Class<T> bean) {
return context.getBean(beanName, bean);
}

public static <T> T getBeanOrNull(Class<T> bean) {
try {
return context.getBean(bean);
} catch (Exception e) {
return null;
}
}

/**
* 获取配置
*
* @param key 配置key
* @return 配置value
*/
public static String getConfig(String key) {
return environment.getProperty(key);
}

public static String getConfigOrElse(String mainKey, String slaveKey) {
String ans = environment.getProperty(mainKey);
if (ans == null) {
return environment.getProperty(slaveKey);
}
return ans;
}

/**
* 获取配置
*
* @param key 配置的key
* @param val 配置不存在时的默认值
* @return 配置的value
*/
public static String getConfig(String key, String val) {
return environment.getProperty(key, val);
}

/**
* 发布事件消息
*
* @param event 事件
*/
public static void publishEvent(ApplicationEvent event) {
context.publishEvent(event);
}
}

需要注意的是,这个上下文工具类自身必须是一个Bean,需要加上@Component注解。

ConfigurableApplicationContext

接下来我们还需要了解一下ConfigurableApplicationContext这个类。
在上文的上下文工具类中,我不仅获取类Spring Context,我还写了SpringContext.configurableContext = (ConfigurableApplicationContext) applicationContext;这句代码,目的是什么呢?
我们先反编译一下,看看他的源码。
image.png
这里我们可以看到,ConfigurableApplicationContext接口继承了ApplicationContext接口,所以我直接转换类型是没问题的,至于为什么要转换,是因为ConfigurableApplicationContext接口中有一个额外的方法需要用到。

1
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

ConfigurableListableBeanFactory getBeanFactory()方法,可以获取到一个ConfigurableListableBeanFactory类。
这个方法的最终实现是在GenericApplicationContext类。
image.png
而在GenericApplicationContext类中,构造函数如下:

1
2
3
4
5
public GenericApplicationContext() {
this.customClassLoader = false;
this.refreshed = new AtomicBoolean();
this.beanFactory = new DefaultListableBeanFactory();
}

this.beanFactory = new DefaultListableBeanFactory();这个代码,实际上就是在初始化一个默认的Bean工厂实例,而这个实例恰恰就是操作Spring IOC的一个关键。
在这个类中还存在获取这个Bean工厂实例的方法:

1
2
3
public final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}

注:ConfigurableListableBeanFactorysDefaultListableBeanFactory的父类。

所以,在上下文工具类中,我们可以通过获取到的ConfigurableApplicationContext来获取到ConfigurableListableBeanFactory,从而达到插手SpringBean实例的注册与销毁。

1
ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();

ConfigurableListableBeanFactory

DefaultListableBeanFactoryConfigurableListableBeanFactory的实现,我们可以查看DefaultListableBeanFactory中有关Bean操作的源码。
DefaultListableBeanFactory又是最原始Bean工厂的实现,所以他可以直接对Bean进行操作,我们可以看一下它的类图,不得不说Spring的源码的结构设计真的是很精妙。
image.png

注册与销毁Bean

说得有些啰嗦,不过我希望在知道怎么用的同时可以知晓这个方法从何而来,这样会有深入一点的理解,现在开始正题,如何使用ConfigurableListableBeanFactory去注册与销毁Bean呢?
ConfigurableListableBeanFactory存在以下方法,用于注册与销毁Bean

1
2
3
4
// author JanYork
ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
beanFactory.registerSingleton(); //参数不展示
beanFactory.destroyBean(); //参数不展示
  1. 注册Bean方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 注册单例Bean
*
* @param beanName 名称
* @param singletonObject 实例对象
*/
public static void registerSingleton(String beanName, Object singletonObject) {
ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
// 如果已经存在,则先销毁
if (beanFactory.containsSingleton(beanName)) {
unregisterSingleton(beanName);
}
beanFactory.registerSingleton(beanName, singletonObject);
}

/**
* 注册单例Bean
*
* @param beanClass 类
* @param singletonObject 实例对象
*/
public static void registerSingleton(Class<?> beanClass, Object singletonObject) {
String beanName = beanClass.getName();
ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
// 如果已经存在,则先销毁
if (beanFactory.containsSingleton(beanName)) {
unregisterSingleton(beanClass);
}
beanFactory.registerSingleton(beanName, singletonObject);
}
  1. 销毁Bean方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 注销Bean
*
* @param beanName 名称
*/
public static void unregisterSingleton(String beanName) {
ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
if (beanFactory instanceof DefaultListableBeanFactory defaultListableBeanFactory) {
// 首先确保销毁该bean的实例(如果该bean实例是一个单例的话)
if (defaultListableBeanFactory.containsSingleton(beanName)) {
defaultListableBeanFactory.destroySingleton(beanName);
}
// 然后从容器的bean定义注册表中移除该bean定义
if (defaultListableBeanFactory.containsBeanDefinition(beanName)) {
defaultListableBeanFactory.removeBeanDefinition(beanName);
}
}
}

/**
* 注销Bean
*
* @param beanClass 类
*/
public static void unregisterSingleton(Class<?> beanClass) {
String beanName = beanClass.getName();
ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
if (beanFactory instanceof DefaultListableBeanFactory defaultListableBeanFactory) {
// 首先确保销毁该bean的实例(如果该bean实例是一个单例的话)
if (defaultListableBeanFactory.containsSingleton(beanName)) {
defaultListableBeanFactory.destroySingleton(beanName);
}
// 然后从容器的bean定义注册表中移除该bean定义
if (defaultListableBeanFactory.containsBeanDefinition(beanName)) {
defaultListableBeanFactory.removeBeanDefinition(beanName);
}
}
}

注:我这里的销毁方法没有直接使用beanFactory.destroyBean(),因为我可能需要在销毁的时候干些什么,我这里使用defaultListableBeanFactory来销毁Bean,它分为两个部分,一个是销毁Bean的实例,一个是销毁Bean的注册表信息。

之所以要将beanFactory(ConfigurableListableBeanFactory)转换到DefaultListableBeanFactory是因为DefaultListableBeanFactory实现了DefaultSingletonBeanRegistry,否则单纯使用ConfigurableListableBeanFactory他是无法使用defaultListableBeanFactory中的containsSingletondestroySingletoncontainsBeanDefinitionremoveBeanDefinition方法的,具体原因可以查看类图。

OK,那么有了这些方法,我们可以通过上下文工具类来直接插手Bean的注册与销毁,这样我们就可以实现动态的去更新Bean来,比如:某短信服务的Key与密钥存储在数据库,Spring程序启动时将读取数据库中短信服务配置的相关信息初始化短信服务的实例对象,当我们调用更改短信服务在数据库的配置时,我们可以在修改后调用上下文的Bean销毁与注册方法,实现Bean的动态更新。

如果使用了微服务,就不必如此了,因为注册中心与配置中心基本上会存在动态配置与动态刷新Bean的某些操作,比如Nacos@RefreshScope注解。

总结

本文介绍了如何使用Spring实现动态注册和更新Bean的功能。通过创建Bean定义并将其注册到Spring容器中,我们可以在应用程序运行时动态管理Bean
这对于构建灵活的应用程序和插件系统非常有用。
请注意,动态注册和更新Bean是一项强大的功能,但也需要谨慎使用,以避免复杂性和性能问题。根据实际需求和场景选择是否使用这种方法。
希望这篇文章对你有所帮助,我是小简,下篇再见。