【问题标题】:Spring boot adding and removing singleton at runtimeSpring boot 在运行时添加和删除单例
【发布时间】:2019-02-23 05:39:29
【问题描述】:

参考下面的链接,我希望我的 Spring Boot 应用程序在运行时在 applicationcontext 中替换 bean。

Add Bean Remove Bean

下面是我的尝试,

MainClass.java

@SpringBootApplication
public class MainClass {

public static void main(String[] args) {
        SpringApplication.run(
                MainClass.class, args);

        new Thread(new MyThread()).run();
    }
}

ApplicationContextProvider.java

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.ConfigurableApplicationContext;

    public class ApplicationContextProvider implements ApplicationContextAware {
        private static ApplicationContext context;

    public static ApplicationContext getApplicationContext(){
        return context;
    }

    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        context = arg0;

    }

    public Object getBean(String name){
        return context.getBean(name, Object.class);
    }

    public void addBean(String beanName, Object beanObject){
        ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
        beanFactory.registerSingleton(beanName, beanObject);
    }

    public void removeBean(String beanName){
        BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
        reg.removeBeanDefinition(beanName);
    }
}

Config.java

@Configuration
@ComponentScan(value="com.en.*")
public class Config {

    @Bean
    @Qualifier("myMap")
    public MapBean myMap(){

        MapBean bean = new MapBean();

        Map<String, String> mp = new HashMap<>();
        mp.put("a", "a");
        bean.setMp(mp);
        return bean;
    }

    @Bean
    ApplicationContextProvider applicationContextProvider(){
        return new ApplicationContextProvider();
    }

}

MapBean.java

import java.util.Map;

public class MapBean {

    private Map<String, String> mp;

    public Map<String, String> getMp() {
        return mp;
    }

    public void setMp(Map<String, String> mp) {
        this.mp = mp;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("MapBean [mp=");
        builder.append(mp);
        builder.append("]");
        return builder.toString();
    }
}

MyThread.java

import java.util.HashMap;
import java.util.Map;

import com.en.model.MapBean;

public class MyThread implements Runnable{

    static ApplicationContextProvider appCtxPrvdr = new ApplicationContextProvider();

    public void run(){
        try {
            Thread.sleep(5000);

            if(ApplicationContextProvider.getApplicationContext().containsBean("myMap")){
                System.out.println("AppCtx has myMap");
                MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
                System.out.println(newM);
                appCtxPrvdr.removeBean("myMap");
                System.out.println("Removed myMap from AppCtx");
            }

            MapBean bean1 = new MapBean();
            Map<String, String> mp = new HashMap<>();
            mp.put("b", "b");
            bean1.setMp(mp);

            appCtxPrvdr.addBean("myMap", bean1);
            System.out.println("myMap added to AppCtx");

            if(ApplicationContextProvider.getApplicationContext().containsBean("myMap")){
                System.out.println("AppCtx has myMap");
                MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
                System.out.println(newM);
                appCtxPrvdr.removeBean("myMap");
                System.out.println("Removed myMap from AppCtx");
            }

            MapBean bean2 = new MapBean();
            Map<String, String> map2 = new HashMap<>();
            map2.put("c", "c");
            bean2.setMp(map2);

            appCtxPrvdr.addBean("myMap", bean2);
            System.out.println("myMap added to AppCtx");
            MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
            System.out.println(newM);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

我得到的输出如下,

AppCtx has myMap
MapBean [mp={a=a}]
Removed myMap from AppCtx
myMap added to AppCtx
AppCtx has myMap
MapBean [mp={b=b}]
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myMap' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.removeBeanDefinition(DefaultListableBeanFactory.java:881)
    at com.en.config.ApplicationContextProvider.removeBean(ApplicationContextProvider.java:47)
    at com.en.config.MyThread.run(MyThread.java:36)
    at java.lang.Thread.run(Unknown Source)
    at com.en.MainClass.main(MainClass.java:77)

所以根据我的理解,下面的事情正在发生。

  1. 在 Config 类中,它正在将 myMap 添加到 appctx。
  2. 在Mythread类中,可以在appctx中找到myMap。
  3. 它可以打印然后从 appctx 中删除。
  4. 可以将新的 myMap 添加到 appctx。
  5. 完成上述步骤后。它无法再次删除它。

请就如何多次添加和删除它提出建议。

【问题讨论】:

  • 既然您将删除和添加 bean 问题都链接了,为什么不使用添加 bean 问题中的一个答案?
  • 尝试了所有答案,但没有得到最终解决方案。

标签: java spring spring-boot applicationcontext


【解决方案1】:

BeanDefinitions 和 beans 在 spring 中是完全不同的东西。当 BeanDefinition 被移除时,该 bean 仍然存在于 ApplicationContext 中。

因此我无法真正理解您的示例中 ApplicationContextProvider 的实现。

现在您要求的东西非常不寻常,如果您能提供更多信息说明为什么在运行时需要这样的逻辑,那就太好了。

我个人认为您不应该在应用程序启动时删除 bean。

有可能或至少有点“传统”:

  • 在应用程序上下文启动时有条件地加载bean,在@Conditional注解(有很多)/@Profile注解的帮助下开始

  • 在运行时更改 bean 以赋予它额外的功能,为此使用 BeanPostProcessor

  • 通过定义 BeanFactoryPostProcessor 来改变 Bean 定义(在极少数情况下使用)

现在,如果您了解所有这些机制,但没有一个适合您的需求,请尝试以下方法:

在单例 bean 中定义一个内部状态,并在每次调用 bean 的方法时检查状态。

这可以直接在 bean 内部实现,使用包装器/装饰器或任何其他方式,但逻辑是相同的。

例子:

public class MySingleton {
   private boolean shouldWork = true;

   public void stop() {
     shouldWork = false;
   }

   public void start() {
     shouldWork = true;
   }


   public void doSomething() {
         if(shouldWork) {
          // do real logic
         }
         else {
          // do nothing, or some minimal thing to not break anything 
         }
   }
}

【讨论】:

  • 我不知道 Conditional & Profile / BeanPostProcessor。我会搜索并回复你。我还需要运行时添加和删除,因为在我的应用程序中,我正在创建套接字对象并且我想在它断开连接时重新连接它。重新连接后,我想在 APPCTX 中存储新的 Socket 对象。上面的代码只是为了简化我的问题。
  • 我通过了 BeanPostProcessor。但我不明白如何更改业务逻辑上的 bean 属性?根据 Spring,postProcessBeforeInitialization 将在 bean 初始化之前调用,而 postProcessAfterInitialization 将在 bean 初始化之后调用。这些方法调用是自动的。你是要我手动调用 postProcessAfterInitialization 吗?
  • 从第一条评论看来,您只需要不创建套接字对象,而是创建一些包含重新连接逻辑的更“复杂”的对象。在这种情况下,您甚至不需要 BeanPostProcessors,因为它们用于比您的更高级的场景中
  • 嗨,Mark Bramnik:我尝试应用 BeanPosProcessor,但它没有反映在应用程序上下文中。我只得到旧对象。你能帮我提供一个完整的例子吗?
  • 我只是不认为你需要一个 bean 后处理器......见我之前的评论。还不够好吗?
【解决方案2】:

好吧,您的逻辑非常连贯,如果您真的想在运行时使用不同的配置刷新 bean 或类似的事情, 请考虑查看externalized configurationsrefresh configs on the fly

但是,如果您仍然对此不满意并且您需要坚持上面所做的,我想问题出在您的方法上:

public void addBean(String beanName, Object beanObject){
    ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
    beanFactory.registerSingleton(beanName, beanObject);
}

因为它没有注册 bean 定义,所以 spring 上下文不会知道它真的存在。 建议尝试添加:

    BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
    reg.registerBeanDefinition(beanName,beanDefinition);

所以基本上你的 addBean 方法应该改变如下,

public void addBean(String beanName, Object beanObject){
    ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
    beanFactory.registerSingleton(beanName, beanObject);
    BeanDefinition beanDefinition = beanFactory.getBeanDefinition( beanName );
    BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
    reg.registerBeanDefinition(beanName,beanDefinition);
}

【讨论】:

  • 如何在 registerBeanDefinition 方法调用中创建 beanDefinition?
  • 已编辑答案,请检查
  • 我也试过了。但它没有用。你可以在你的环境中尝试一下吗?在调用 beanFactory.getBeanDefinition(beanName) 时,它抛出异常为“org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myMap' available”
猜你喜欢
  • 1970-01-01
  • 2012-02-24
  • 1970-01-01
  • 2017-10-11
  • 2021-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多