-1

With reference with below links, i want my spring boot app to replace bean at runtime in applicationcontext.

Add Bean Remove Bean

Below is my try,

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

}

The output i am getting is as below,

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)

So as per my understanding below things are happening.

  1. In Config class, it is adding myMap to appctx.
  2. In Mythread class, it is able to find myMap in appctx.
  3. It is able to print and then remove from appctx.
  4. It is able to add new myMap to appctx.
  5. When above step is done. It is not able to remove it again.

Please advice on how to add and remove it multiple time.

Aditya Ekbote
  • 1,493
  • 3
  • 15
  • 29

2 Answers2

3

BeanDefinitions and beans are totally different things in spring. When BeanDefinition is removed, the bean still exists in ApplicationContext.

Hence I can't really understand the implementation of ApplicationContextProvider in your example.

Now the thing you're asking for is very unusual, it could be great if you could provide more information on why do you need such a logic in runtime.

I personally don't think you should remove the beans when the application starts.

It's possible or at least kind of "conventional" to do the following:

  • Conditionally load the bean when the application context Starts with the help of @Conditional annotation (there are many of those) / @Profile annotation

  • Alter the bean during the runtime to give it additional functionality, for this use BeanPostProcessor

  • Alter Bean definition by means of defining BeanFactoryPostProcessor (used in extremely rare cases)

Now, if you're aware of all these mechanisms and none of them suits your needs, try the following:

Define an internal state in the singleton bean and check the state every time the bean's method is called.

This can be implemented right inside the bean, with wrapper / decorator or in any other way, but the logic is the same.

Example:

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 
         }
   }
}
Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • I am not aware about Conditional & Profile / BeanPostProcessor. I will search on that and get back to you. Also i need runtime addition and deletion because in my application, I am creating socket object and i want to reconnect it when it is disconnect. After reconnecting, I want to Store new Socket object in APPCTX. The above code was just for simplifying my question. – Aditya Ekbote Sep 19 '18 at 06:30
  • I went through BeanPostProcessor. But i dont understand on how can i alter the bean properties on my business logic? As per Spring, postProcessBeforeInitialization will get called before bean initialization and postProcessAfterInitialization will get called after bean has been initialized. These method calls are automatic. Are you telling me to call postProcessAfterInitialization manually? – Aditya Ekbote Sep 19 '18 at 06:58
  • From the first comment it looks like you just need to not create a socket object, but instead create some more "complicated" object that contains the reconnection logic. In this case you don't even need BeanPostProcessors since they're used in more advanced scenarios then yours – Mark Bramnik Sep 19 '18 at 07:03
  • Hi Mark Bramnik: I tried applying BeanPosProcessor but it is not reflecting in application context. I am getting old object only. Can you do me a favor by providing full example? – Aditya Ekbote Sep 19 '18 at 10:05
  • I just dont think you need a bean post processor here... See my previous comment. Is it not good enough? – Mark Bramnik Sep 19 '18 at 10:33
0

Well your logic is pretty wired and if you are really trying to do something like refresh the bean with different configurations at runtime or sort of a thing, Please consider looking at externalized configurations and refresh configs on the fly

But if still you are not happy with this and you need to stick with what you have done above, I guess the problem is with your method :

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

Because since it does not register a bean definition, spring context will not know that its really there. Would suggest to try adding :

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

so basically your addBean method should change as follows,

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);
}
Damith
  • 740
  • 4
  • 12
  • How can i create beanDefinition in registerBeanDefinition method call? – Aditya Ekbote Sep 19 '18 at 06:37
  • Edited the answer, please check – Damith Sep 19 '18 at 06:52
  • 1
    I tried that as well. But it did not work. Can you give it a try in your environment? At call of beanFactory.getBeanDefinition( beanName ), it is throwing exception as "org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myMap' available" – Aditya Ekbote Sep 19 '18 at 07:09