Spring boot добавляет и удаляет синглтон во время выполнения

Со ссылкой на приведенные ниже ссылки я хочу, чтобы мое весеннее загрузочное приложение заменило 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 он может найти myMap в appctx.
  3. Он может распечатать, а затем удалить из appctx.
  4. Он может добавить новую карту myMap в appctx.
  5. Когда вышеуказанный шаг сделан. Снова удалить не получится.

Пожалуйста, посоветуйте, как добавить и удалить его несколько раз.


person Aditya Ekbote    schedule 19.09.2018    source источник
comment
Поскольку вы связываете оба вопроса об удалении и добавлении компонента, почему бы вам не использовать один из ответов в вопросе о добавлении компонента?   -  person daniu    schedule 19.09.2018
comment
Перепробовал все ответы, но не получил окончательного решения.   -  person Aditya Ekbote    schedule 19.09.2018


Ответы (2)


BeanDefinitions и бобы — совершенно разные вещи весной. Когда BeanDefinition удаляется, bean-компонент все еще существует в ApplicationContext.

Следовательно, я не могу понять реализацию ApplicationContextProvider в вашем примере.

Теперь то, о чем вы просите, очень необычно, было бы здорово, если бы вы могли предоставить больше информации о том, зачем вам такая логика во время выполнения.

Я лично не думаю, что вам следует удалять бобы при запуске приложения.

Возможно или, по крайней мере, "обычно" сделать следующее:

  • Условно загружать bean-компонент при запуске контекста приложения с помощью аннотации @Conditional (их много) / аннотации @Profile

  • Измените bean-компонент во время выполнения, чтобы придать ему дополнительную функциональность, для этого используйте BeanPostProcessor

  • Изменить определение Bean с помощью определения BeanFactoryPostProcessor (используется в крайне редких случаях)

Теперь, если вы знаете обо всех этих механизмах и ни один из них не подходит вам, попробуйте следующее:

Определите внутреннее состояние в одноэлементном компоненте и проверяйте состояние каждый раз, когда вызывается метод компонента.

Это можно реализовать прямо внутри бина, с помощью обертки/декоратора или любым другим способом, но логика та же.

Пример:

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 
         }
   }
}
person Mark Bramnik    schedule 19.09.2018
comment
Я не знаю об условном и профиле / BeanPostProcessor. Я поищу об этом и вернусь к вам. Также мне нужно добавление и удаление во время выполнения, потому что в моем приложении я создаю объект сокета и хочу повторно подключить его, когда он отключен. После повторного подключения я хочу сохранить новый объект Socket в APPCTX. Приведенный выше код был предназначен только для упрощения моего вопроса. - person Aditya Ekbote; 19.09.2018
comment
Я прошел через BeanPostProcessor. Но я не понимаю, как я могу изменить свойства bean-компонента в своей бизнес-логике? Согласно Spring, postProcessBeforeInitialization будет вызываться до инициализации bean-компонента, а postProcessAfterInitialization будет вызываться после инициализации bean-компонента. Эти вызовы методов являются автоматическими. Вы предлагаете мне вызвать postProcessAfterInitialization вручную? - person Aditya Ekbote; 19.09.2018
comment
Из первого комментария кажется, что вам просто нужно не создавать объект сокета, а вместо этого создать какой-то более сложный объект, содержащий логику переподключения. В этом случае вам даже не нужны BeanPostProcessors, поскольку они используются в более сложных сценариях, чем ваши. - person Mark Bramnik; 19.09.2018
comment
Привет, Марк Брамник. Я пытался применить BeanPosProcessor, но это не отражается в контексте приложения. Я получаю только старый объект. Можете ли вы сделать мне одолжение, предоставив полный пример? - person Aditya Ekbote; 19.09.2018
comment
Я просто не думаю, что вам нужен здесь постпроцессор bean... См. мой предыдущий комментарий. Разве это не достаточно хорошо? - person Mark Bramnik; 19.09.2018

Ну, ваша логика довольно запутана, и если вы действительно пытаетесь сделать что-то вроде обновления bean-компонента с различными конфигурациями во время выполнения или что-то в этом роде, рассмотрите возможность просмотра внешние конфигурации и обновлять конфигурации на лету

Но если вы все еще недовольны этим и вам нужно придерживаться того, что вы сделали выше, я думаю, проблема в вашем методе:

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

Потому что, поскольку он не регистрирует определение компонента, 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);
}
person Damith    schedule 19.09.2018
comment
Как я могу создать beanDefinition в вызове метода registerBeanDefinition? - person Aditya Ekbote; 19.09.2018
comment
Отредактировал ответ, пожалуйста, проверьте - person Damith; 19.09.2018
comment
Я тоже так пробовал. Но это не сработало. Можете ли вы попробовать это в своей среде? При вызове beanFactory.getBeanDefinition(beanName) он генерирует исключение как org.springframework.beans.factory.NoSuchBeanDefinitionException: нет доступного bean-компонента с именем myMap - person Aditya Ekbote; 19.09.2018