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

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;
 }

注:ConfigurableListableBeanFactorysDefaultListableBeanFactory的父类。

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

java 复制代码
 ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();

ConfigurableListableBeanFactory

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

注册与销毁Bean

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

java 复制代码
 // author JanYork
 ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
 beanFactory.registerSingleton(); //参数不展示
 beanFactory.destroyBean(); //参数不展示
  1. 注册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);
 }
  1. 销毁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中的containsSingletondestroySingletoncontainsBeanDefinitionremoveBeanDefinition方法的,具体原因可以查看类图。

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

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

总结

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

相关推荐
guoruijun_2012_43 分钟前
fastadmin多个表crud连表操作步骤
android·java·开发语言
Hello-Brand13 分钟前
Java核心知识体系10-线程管理
java·高并发·多线程·并发·多线程模型·线程管理
乐悠小码19 分钟前
数据结构------队列(Java语言描述)
java·开发语言·数据结构·链表·队列
史努比.21 分钟前
Pod控制器
java·开发语言
2的n次方_23 分钟前
二维费用背包问题
java·算法·动态规划
皮皮林55124 分钟前
警惕!List.of() vs Arrays.asList():这些隐藏差异可能让你的代码崩溃!
java
莳光.24 分钟前
122、java的LambdaQueryWapper的条件拼接实现数据sql中and (column1 =1 or column1 is null)
java·mybatis
程序猿麦小七29 分钟前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
weisian15135 分钟前
认证鉴权框架SpringSecurity-2--重点组件和过滤器链篇
java·安全
蓝田~37 分钟前
SpringBoot-自定义注解,拦截器
java·spring boot·后端