springboot 重新注册 bean

项目中,有时候会遇到这样的需求:更新配置后,需要重新处理相关的业务,但是不想重启应用。例如 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<RefreshEvent> {
    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 {


    /**
     * <pre>
     * 注册单例Bean
     * 注意:重新注册 bean 之后,如果依赖者需要声明有 {@link org.springframework.cloud.context.config.annotation.RefreshScope} 才会更新注入
     *
     * 参考来源:https://cloud.tencent.com/developer/article/2393796
     * </pre>
     * @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);
    }

    /**
     * <pre>
     * 注册单例Bean
     * 注意:重新注册 bean 之后,如果依赖者需要声明有 {@link org.springframework.cloud.context.config.annotation.RefreshScope} 才会更新注入
     *
     * 参考来源:https://cloud.tencent.com/developer/article/2393796
     * </pre>
     * @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);
    }

    /**
     * <pre>
     * 注销Bean
     *
     * 参考来源:https://cloud.tencent.com/developer/article/2393796
     * </pre>
     * @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);
            }
        }
    }

    /**
     * <pre>
     * 注销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-腾讯云开发者社区-腾讯云 

相关推荐

  1. springboot 重新注册 bean

    2024-07-19 22:58:01       22 阅读
  2. SpringBoot中WebSokcet无法注入Bean对象的解决方案

    2024-07-19 22:58:01       47 阅读
  3. SpringBoot】如何在 Utils 工具类中注入 Bean

    2024-07-19 22:58:01       46 阅读
  4. 基于SpringBoot注入Bean形式的监听(端口)

    2024-07-19 22:58:01       23 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 22:58:01       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 22:58:01       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 22:58:01       45 阅读
  4. Python语言-面向对象

    2024-07-19 22:58:01       55 阅读

热门阅读

  1. 什么是分布式事务?有哪些实现方案?

    2024-07-19 22:58:01       14 阅读
  2. 讲一讲你理解的虚拟内存

    2024-07-19 22:58:01       21 阅读
  3. Android init.rc如何并行执行任务

    2024-07-19 22:58:01       22 阅读
  4. 如何用BeautifulSoup批量下载美女图片

    2024-07-19 22:58:01       16 阅读
  5. 华为OD机考题(基础API)

    2024-07-19 22:58:01       17 阅读
  6. 几种典型的锁

    2024-07-19 22:58:01       15 阅读
  7. 细水长流:使用Scikit-Learn实现模型的增量预测

    2024-07-19 22:58:01       20 阅读
  8. 云计算复习--云计算机制

    2024-07-19 22:58:01       19 阅读