Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace a bean inside the spring container during run-time

Tags:

java

spring

Suppose I define a bean (eg BeanA) inside the Spring container, and this bean is injected into an object. (eg BeanAUser)

During run-time, can I use another bean instance to replace the original BeanA inside the spring container?? And also re-injects this new bean instance into BeanAUser in order to replace the original BeanA?

like image 367
Ken Chan Avatar asked Feb 05 '12 06:02

Ken Chan


People also ask

Which method is called just before the bean is about be destroyed inside bean container?

2.4. @PreDestroy annotated method is invoked just before the bean is about be destroyed inside bean container.

How does a Spring container destroy beans?

And finally, the bean is destroyed when the spring container is closed. Therefore, if we want to execute some code on the bean instantiation and just after closing the spring container, then we can write that code inside the custom init() method and the destroy() method.

What is life cycle of bean in Spring container?

Spring provides the following two callback interfaces: InitializingBean : Declares the afterPropertiesSet() method which can be used to write the initialization logic. The container calls the method after properties are set. DisposableBean : Declares the destroy() method which can be used to write any clean up code.


4 Answers

It can be easily achieved using a proxy. Create a delegating implementation of your interface and switch object it is delegating to.

@Component("BeanA")
public class MyClass implements MyInterface {
  private MyInterface target;

  public void setTarget(MyInterface target) {
    this.target = target;
  }

  // now delegating implementation of MyInterface methods
  public void method1(..) {
    this.target.method1(..);
  }

  ..
}
like image 51
mrembisz Avatar answered Oct 11 '22 09:10

mrembisz


Spring introduced the new RefreshScope to replace a bean at runtime. Internally, a proxy is created as described in the answer of mrembisz.

@RefreshScope
@Component
public class MyBean { ... }
like image 37
rwitzel Avatar answered Oct 11 '22 07:10

rwitzel


The way I would do this is by using a system called arbitrary-method-replacement.

Create a class that implements org.springframework.beans.factory.support.MethodReplacer, this will force you to create a method like so

public Object reimplement(Object o, Method m, Object[] args) throws Throwable

The parameters mean the following:

  • o - the bean instance you're replacing a method on
  • m - the method meta we are replacing
  • args - the method arguments supplied (if any)

So I would imagine your class to look something like the following

public BeanAUserHelper implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {

        if (some expression){
            return beanA;
        }
        else {
            return beanB;
        }
    }
}

In your bean configuration, you then instruct Spring to replace the getBeanX() method on your BeanAUser like so

<!-- this is the bean who needs to get a different instance -->
<bean id="beanAUser" class="a.b.c.BeanAUser">
    <!-- arbitrary method replacement -->
    <replaced-method name="getBeanX" replacer="beanAUserHelper"/>
</bean>

<!-- this is your 'dynamic bean getter' -->
<bean id="beanAUserHelper" class="a.b.c.BeanAUserHelper"/>

I hope I understood your problem correctly :)

like image 6
Stefan Z Camilleri Avatar answered Oct 11 '22 07:10

Stefan Z Camilleri


Assuming MyClass in mrembisz's answer is not final, one doesn't have to implement decorator pattern manually and can implement it automatically using BeanPostProcessor. First define extension interface for injecting new delegating implementation:

public interface Wrapper extends MyInterface {
    void setTarget(MyInterface target);
}

Then create BeanPostProcessor which wraps all implementations of MyInterface to CGLIB proxy. Proxy acts as both MyClass (which enables it to be injected into fields of MyClass type) and Wrapper (which enables it to change target). Proxy redirects all original invocations to MyClass target (which is initially set to value declared in Spring), invocation of Wrapper.setTarget results in target change.

@Component
public static class MyPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Object result = bean;
        if (bean instanceof MyInterface) {
            final MyInterface myInterface = (MyInterface)bean;
            Class<? extends MyInterface> clazz = myInterface.getClass();
            if (!isFinal(clazz.getModifiers())) {
                result = Enhancer.create(clazz, new Class[] {MyInterface.class}, new MethodInterceptor() {
                    private MyInterface target = myInterface;
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                        if (method.getName().equals("setTarget") && method.getDeclaringClass().equals(Wrapper.class) && method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(MyInterface.class)) {
                            this.target = (MyInterface)args[0];
                            return null;
                        } else {
                            Object result = proxy.invoke(this.target, args);
                            return result;
                        }
                    }
                });
            }
        }
        return result;
    }

}

Simply the idea is: define bean in Spring as it was a normal bean, tweak it after initialization.

like image 5
Tomáš Záluský Avatar answered Oct 11 '22 07:10

Tomáš Záluský