Spring启动时在运行时添加和删除单例

问题描述 投票:-1回答:2

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

Add Bean Remove Bean

以下是我的尝试,

main class.Java

@SpringBootApplication
public class MainClass {

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

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

application context provider.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();
    }

}

map bean.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();
    }
}

my thread.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. 完成上述步骤。它无法再次删除它。

请告知如何多次添加和删除它。

java spring spring-boot applicationcontext
2个回答
3
投票

BeanDefinitions和bean在春天是完全不同的东西。删除BeanDefinition后,该Bean仍存在于ApplicationContext中。

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

现在您要求的东西是非常不寻常的,如果您可以提供有关为什么在运行时需要这样的逻辑的更多信息,那可能会很棒。

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

执行以下操作可能或至少是“常规”类型:

  • 在应用程序上下文的帮助下,在@Conditional批注(有很多这样的东西)/ @Profile注释的帮助下有条件地加载bean
  • 在运行时更改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 
         }
   }
}

0
投票

那么你的逻辑是非常有线的,如果你真的想做一些事情,比如在运行时用不同的配置刷新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);
}
© www.soinside.com 2019 - 2024. All rights reserved.