springboot 重新注册 bean
创始人
2024-12-26 11:34:49
0

项目中,有时候会遇到这样的需求:更新配置后,需要重新处理相关的业务,但是不想重启应用。例如 elasticsearch 证书过期后,需要更换 http_ca.crt ,但是又不想重启应用。

本人对 spring IOC 的源码不算深入,只知道可以实现,捣鼓了大半天,终于实现了,特意记录下过程。写个 demo ,希望有需要的人可以参考一下。

使用 @Bean 来创建注册业务逻辑 bean。当配置更新后,在监听事件里,创建新的业务逻辑 bean,处理业务逻辑,然后使用 beanFactory 销毁旧的业务逻辑 bean 后再重新注册新的业务逻辑bean 

本人使用的是 nacos 配置中心,这里没有给出 helloWorld.yaml 文件,请自行创建

ConfigCenterConfigRefreshed

 import com.alibaba.fastjson2.JSONObject; import com.alibaba.nacos.api.config.listener.AbstractSharedListener; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.endpoint.event.RefreshEvent; import org.springframework.context.ApplicationListener;  import java.lang.reflect.Field;  /**  * nacos 配置中心的配置文件修改的事件监听。  * 注意:只有启用自动刷新的配置才会收到配置中心文件修改从而才会触发事件。  * @Author: wuYaFang  * @Date: 2022/5/5  */ public interface ConfigCenterConfigRefreshed extends ApplicationListener {     String getDataId();     String getGroup();      @Override     default void onApplicationEvent(RefreshEvent event){         Object source1 = event.getSource();         if (source1 == null || !(source1 instanceof AbstractSharedListener)) {             return;         }         AbstractSharedListener source = (AbstractSharedListener) event.getSource();         Field[] declaredFields = AbstractSharedListener.class.getDeclaredFields();         JSONObject obj = new JSONObject();         for (int i = 0; i < declaredFields.length; i++) {             try {                 Field field = declaredFields[i];                 field.setAccessible(true);                 Object o = field.get(source);                 obj.put(field.getName(), o);                 field.setAccessible(false);             } catch (IllegalAccessException ex) {                 ex.printStackTrace();                 return;             }         }         Source source2 = obj.toJavaObject(Source.class);         if (!StringUtils.equals(source2.getDataId(), getDataId()) || !StringUtils.equals(source2.getGroup(), getGroup())) {             return;         }         refreshed(source2);      }      /**      * 配置更新之后的处理。      * @param source      */     default void refreshed(Source source) {      }      @Data     class Source {         private String dataId;         private String group;     } }  

HelloWorldConfig

import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component;  @Data @Component @RefreshScope @ConfigurationProperties(prefix = "hello", ignoreInvalidFields = true) public class HelloWorldConfig {      private String name; }

HelloWorldLogic

package com.demo;  import lombok.Data; import lombok.extern.slf4j.Slf4j;  @Data @Slf4j public class HelloWorldLogic {      private HelloWorldConfig helloWorldConfig;     public void say() {         log.info("hello-------------{}", helloWorldConfig);     } } 

HelloWorldConfiguration 

package com.demo;  import com.hmc.cloud.util.api.ConfigCenterConfigRefreshed; import com.hmc.common.util.HmcSpringBeanUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  @Configuration public class HelloWorldConfiguration implements ConfigCenterConfigRefreshed {      @Autowired     private DefaultListableBeanFactory beanFactory;     @Autowired     private HelloWorldConfig helloWorldConfig;      @Bean     public HelloWorldLogic helloWorldLogic() {         return createHelloWorldLogic();     }      public HelloWorldLogic createHelloWorldLogic() {         HelloWorldLogic logic = new HelloWorldLogic();         return logic;     }      @Override     public String getDataId() {         return "helloWorld.yaml";     }      @Override     public String getGroup() {         return "DEFAULT";     }      @Override     public void refreshed(Source source) {         // 在 nacos 中修改发布 helloWorld.yaml 之后,更新了 HelloWorldConfig 之后,会调用 此方法 refreshed(Source source)         // 重新创建 HelloWorldLogic 实例         HelloWorldLogic logic = createHelloWorldLogic();         // 处理业务逻辑         logic.setHelloWorldConfig(helloWorldConfig);         logic.say();         // 重新注册到 IOC 容器中, 然后会将新的 helloWorld 实例更新到 HelloWorldController 中。         // 需要注意,需要在  HelloWorldController 类上声明 @RefreshScope         HmcSpringBeanUtil.registerSingleton("helloWorldLogic", logic, beanFactory);     } } 

HelloWorldController

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;  @RefreshScope @RestController public class HelloWorldController {      @Autowired     private HelloWorldLogic helloWorldLogic;      @GetMapping("/test/helloWorld")     public HelloWorldConfig helloWorld() {         helloWorldLogic.say();         return helloWorldLogic.getHelloWorldConfig();     } }

HmcSpringBeanUtil

import org.springframework.beans.factory.config.NamedBeanHolder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition;  /**  * spring bean 的辅助工具  * @author wuYaFang  * @date 2024/7/17 21:23  */ public class HmcSpringBeanUtil {       /**      * 
      * 注册单例Bean      * 注意:重新注册 bean 之后,如果依赖者需要声明有 {@link org.springframework.cloud.context.config.annotation.RefreshScope} 才会更新注入      *      * 参考来源:https://cloud.tencent.com/developer/article/2393796      * 
* @param beanName 名称 * @param singletonObject 实例对象 */ public static void registerSingleton(String beanName, Object singletonObject, DefaultListableBeanFactory beanFactory) { // 如果已经存在,则先销毁 if (beanFactory.containsSingleton(beanName)) { unregisterSingleton(beanName,beanFactory); } beanFactory.registerSingleton(beanName, singletonObject); } /** *
      * 注册单例Bean      * 注意:重新注册 bean 之后,如果依赖者需要声明有 {@link org.springframework.cloud.context.config.annotation.RefreshScope} 才会更新注入      *      * 参考来源:https://cloud.tencent.com/developer/article/2393796      * 
* @param beanClass 类 * @param singletonObject 实例对象 */ public static void registerSingleton(Class beanClass, Object singletonObject, DefaultListableBeanFactory beanFactory) { String beanName = beanClass.getName(); // 如果已经存在,则先销毁 if (beanFactory.containsSingleton(beanName)) { unregisterSingleton(beanClass, beanFactory); } RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(beanClass); NamedBeanHolder namedBeanHolder = beanFactory.resolveNamedBean(beanClass); beanFactory.registerBeanDefinition(namedBeanHolder.getBeanName(), rootBeanDefinition); beanFactory.registerSingleton(beanName, singletonObject); } /** *
      * 注销Bean      *      * 参考来源:https://cloud.tencent.com/developer/article/2393796      * 
* @param beanName * @param beanFactory */ public static void unregisterSingleton(String beanName, DefaultListableBeanFactory beanFactory) { if (beanFactory instanceof DefaultListableBeanFactory) { // 首先确保销毁该bean的实例(如果该bean实例是一个单例的话) if (beanFactory.containsSingleton(beanName)) { beanFactory.destroySingleton(beanName); } // 然后从容器的bean定义注册表中移除该bean定义 if (beanFactory.containsBeanDefinition(beanName)) { beanFactory.removeBeanDefinition(beanName); } } } /** *
      * 注销Bean      * 参考来源:https://cloud.tencent.com/developer/article/2393796      * @param beanClass 类      */     public static void unregisterSingleton(Class beanClass, DefaultListableBeanFactory beanFactory) {         String beanName = beanClass.getName();         if (beanFactory instanceof DefaultListableBeanFactory) {             // 首先确保销毁该bean的实例(如果该bean实例是一个单例的话)             if (beanFactory.containsSingleton(beanName)) {                 beanFactory.destroySingleton(beanName);             }             // 然后从容器的bean定义注册表中移除该bean定义             if (beanFactory.containsBeanDefinition(beanName)) {                 beanFactory.removeBeanDefinition(beanName);             }         }     } }

bootstrap yaml 配置

spring:   cloud:     nacos:       config:         server-addr: ${nacos.config.server-addr}         username: ${nacos.config.username}         password: ${nacos.config.password}         namespace:  ${nacos.config.namespace}         enable-remote-sync-config: on         group: ${model}         file-extension: yaml         prefix: ${model}         extension-configs:           - dataId: helloWorld.yaml             group: hmc-global             refresh: true

参考:SpringBoot动态注册与更新IOC中的Bean-腾讯云开发者社区-腾讯云 

相关内容

热门资讯

2024教程"海南骨... 2024教程"海南骨牌辅助器免费"好像是真的有辅助教程(有挂技巧)-哔哩哔哩1、海南骨牌辅助器免费模...
透视技法!wepoker免费脚... 透视技法!wepoker免费脚本咨询(透视)开挂透视软件(哔哩哔哩)1、实时wepoker免费脚本咨...
5分钟脚本!新兴茶苑万能辅助器... 5分钟脚本!新兴茶苑万能辅助器,长城互娱辅助(总是真的有辅助下载)-哔哩哔哩1、新兴茶苑万能辅助器脚...
第九分钟方式!xpoker辅助... 第九分钟方式!xpoker辅助器(透视)真是有辅助下载(哔哩哔哩)xpoker辅助器透视方法中分为三...
记者揭秘!"蜀山四川... 记者揭秘!"蜀山四川辅助脚本"竟然真的有辅助教程(有挂方法)-哔哩哔哩1.蜀山四川辅助脚本 选牌创建...
透视办法!wejoker辅助软... 透视办法!wejoker辅助软件价格(透视)开挂脚本安装(哔哩哔哩)1、首先打开wejoker辅助软...
第2分钟插件!奇迹陕西抢红包辅... 第2分钟插件!奇迹陕西抢红包辅助器,拱趴大菠萝辅助工具(本来是真的辅助下载)-哔哩哔哩;1、打开软件...
第5分钟技法!hhpoker的... 第5分钟技法!hhpoker的辅助是真的吗(透视)竟然真的是有辅助透视(哔哩哔哩)亲,关键说明,hh...
我来向大家传授"微乐... 我来向大家传授"微乐小程序游戏破解器"一贯是有辅助神器(有挂头条)-哔哩哔哩亲,关键说明,微乐小程序...
透视教程书!wepoker透视... 透视教程书!wepoker透视脚本视频(透视)开挂透视app(哔哩哔哩)1、进入到wepoker透视...