We have a Spring application responsible for doing some integration work that is expected to be up and running 24 x 7.
One of the integration patterns we use is JMS messaging using a bunch of clustered IBM MQ queue managers. With clustered IBM MQ queue managers if one of the queue manager goes down you need to reinstantiate the Queue Connection Factory beans in order for the connectivity to work with the remaining queue managers that are still up.
We put in place JMS health checks and the status of the IBM MQ queue managers changes we programatically restart the spring context and as a result the Queue Connection Factory beans get recreated and everything works just fine.
However this is not ideal for us because other parts of our application not related to IBM MQ works just fine and actually can be in the middle of processing stuff. Because of this we would prefer to only recreate the JMS context, pretty much recreating the Queue Connection Factory beans and a few other beans that depend on those (message listener containers, JmsTemplats, etc) rather than restarting the whole application context.
I found this post how-to-reinitialize-a-spring-bean and tried to implement solution 2)Delete & Register bean in registry but I could not get it to work.
// If I call below:
beanFactory.destroySingleton(qcfName);
var qcf = jmsConfig.connectionFactory();
beanFactory.registerSingleton(qcfName, qcf);
// I get this exception:
Could not register object [org.springframework.cloud.sleuth.instrument.messaging.LazyConnectionFactory@5a0fb814] under bean name 'connectionFactory':
there is already object [org.springframework.cloud.sleuth.instrument.messaging.LazyConnectionFactory@5a0fb814] bound
// If I call below:
beanFactory.destroySingleton(qcfName);
beanFactory.removeBeanDefinition(qcfName);
var qcf = jmsConfig.connectionFactory();
beanFactory.registerSingleton(qcfName, qcf);
// I am getting this exception:
No bean named 'connectionFactory' available
I tried some other permutations such as destroyBean
but I could not get it to work.
Any idea what I am doing wrong and how should I fix it? Thank you in advance for your inputs.
CodePudding user response:
I understand what you wan to do, so I tested a few ways and got this:
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
AnnotationConfigReactiveWebServerApplicationContext context;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
context.registerBean("jms", Bean1.class, () -> new Bean1());
var jmsBean1 = context.getBean(Bean1.class);
jmsBean1.hello();
var jmsBean2 = context.getBean(Bean1.class);
context.removeBeanDefinition("jms");
context.registerBean("jms", Bean1.class, () -> new Bean1());
var jmsBean3 = context.getBean(Bean1.class);
System.out.println(jmsBean1.hashCode());
System.out.println(jmsBean2.hashCode());
System.out.println(jmsBean1 == jmsBean2);
System.out.println(jmsBean3.hashCode());
System.out.println(jmsBean1 == jmsBean3);
}
}
class Bean1 {
public void hello() {
System.out.println("hello");
}
}
the result is :
hello
405390165 // jmsBean1 hashcode -> singleton
405390165 //jmsBean2 hashcode -> singleton
true // jmsBean1 == jmsBean2
1182556970 // jmsBean3 hashcode
false // jmsBean3 is different object
As you can see the default bean scope is singleton when you register new bean.
Note : after remove bean definition try not to keep reference to old object because of JVM GC
tested on :
Spring boot (v2.6.3)
spring framework (5.3.15)
My test project was Reactive Web project because of that you saw ReacttiveApplicationContext injected, depends on your project you have to inject your context type.
hope this be useful.