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
。
java
@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;
}
利用这个方法,我们可以实现一个上下文工具类,如下:
java
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;
这句代码,目的是什么呢? 我们先反编译一下,看看他的源码。 这里我们可以看到,ConfigurableApplicationContext
接口继承了ApplicationContext
接口,所以我直接转换类型是没问题的,至于为什么要转换,是因为ConfigurableApplicationContext
接口中有一个额外的方法需要用到。
csharp
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
ConfigurableListableBeanFactory getBeanFactory()
方法,可以获取到一个ConfigurableListableBeanFactory
类。 这个方法的最终实现是在GenericApplicationContext
类。 而在GenericApplicationContext
类中,构造函数如下:
java
public GenericApplicationContext() {
this.customClassLoader = false;
this.refreshed = new AtomicBoolean();
this.beanFactory = new DefaultListableBeanFactory();
}
而this.beanFactory = new DefaultListableBeanFactory();
这个代码,实际上就是在初始化一个默认的Bean
工厂实例,而这个实例恰恰就是操作Spring IOC
的一个关键。 在这个类中还存在获取这个Bean
工厂实例的方法:
java
public final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
注:
ConfigurableListableBeanFactorys
是DefaultListableBeanFactory
的父类。
所以,在上下文工具类中,我们可以通过获取到的ConfigurableApplicationContext
来获取到ConfigurableListableBeanFactory
,从而达到插手SpringBean
实例的注册与销毁。
java
ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
ConfigurableListableBeanFactory
DefaultListableBeanFactory
是ConfigurableListableBeanFactory
的实现,我们可以查看DefaultListableBeanFactory
中有关Bean
操作的源码。 而DefaultListableBeanFactory
又是最原始Bean
工厂的实现,所以他可以直接对Bean
进行操作,我们可以看一下它的类图,不得不说Spring
的源码的结构设计真的是很精妙。
注册与销毁Bean
说得有些啰嗦,不过我希望在知道怎么用的同时可以知晓这个方法从何而来,这样会有深入一点的理解 ,现在开始正题,如何使用ConfigurableListableBeanFactory
去注册与销毁Bean
呢? ConfigurableListableBeanFactory
存在以下方法,用于注册与销毁Bean
。
java
// author JanYork
ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
beanFactory.registerSingleton(); //参数不展示
beanFactory.destroyBean(); //参数不展示
- 注册Bean方法
java
/**
* 注册单例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);
}
- 销毁Bean方法
java
/**
* 注销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
中的containsSingleton
、destroySingleton
、containsBeanDefinition
、removeBeanDefinition
方法的,具体原因可以查看类图。
OK,那么有了这些方法,我们可以通过上下文工具类来直接插手Bean
的注册与销毁,这样我们就可以实现动态的去更新Bean
来,比如:某短信服务的Key与密钥存储在数据库,Spring
程序启动时将读取数据库中短信服务配置的相关信息初始化短信服务的实例对象,当我们调用更改短信服务在数据库的配置时,我们可以在修改后调用上下文的Bean
销毁与注册方法,实现Bean
的动态更新。
如果使用了微服务,就不必如此了,因为注册中心与配置中心基本上会存在动态配置与动态刷新 Bean
的某些操作,比如 Nacos
的 @RefreshScope
注解。
总结
本文介绍了如何使用Spring
实现动态注册和更新Bean
的功能。通过创建Bean
定义并将其注册到Spring
容器中,我们可以在应用程序运行时动态管理Bean
。 这对于构建灵活的应用程序和插件系统非常有用。 请注意,动态注册和更新Bean是一项强大的功能,但也需要谨慎使用,以避免复杂性和性能问题。根据实际需求和场景选择是否使用这种方法。 希望这篇文章对你有所帮助,我是小简,下篇再见。