33

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?

joragupra
  • 692
  • 1
  • 12
  • 23
Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • I don't think that this is possible. Probably you should consider other solutions. But let's see whether soemone can come up with a solution. – tkr Feb 05 '12 at 07:52
  • 1
    What is your use case ? Better to know what is compelling you to do this. – Santosh Feb 05 '12 at 13:03

7 Answers7

18

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

  ..
}
mrembisz
  • 12,722
  • 7
  • 36
  • 32
  • 3
    Although this works, wouldn't this cause BeanA to have way too much knowledge... which kinda defeats the scope of using DI in the first place. I would much rather create a dedicated helper class that gets injected with the different bean types, and have it wrap the logic that decides what bean instance to supply. – Stefan Z Camilleri Feb 05 '12 at 19:39
  • 1
    @StefanZCamilleri I don't think I understand. Knowledge about what? This class is as dumb as it gets, it's a proxy with no logic. It expects some external class to do the switch through `setTarget`. The question did not include details allowing any more customized solution. – mrembisz Feb 05 '12 at 21:52
  • I misunderstood your approach since you named it BeanA and understood the you would add this functionality to @KenChan's implementation of BeanA... likewise, Spring offers this functionality using arbitrary-method-replacement, so if using Spring, I'd go the Spring-way anyway. Furthermore, it would avoid the need to have other classes involved and would keep all bean-switching logic in one place, whilst doing all this transparently to BeanAUser – Stefan Z Camilleri Feb 05 '12 at 22:25
10

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 { ... }
Community
  • 1
  • 1
rwitzel
  • 1,694
  • 17
  • 21
6

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 :)

Stefan Z Camilleri
  • 4,016
  • 1
  • 32
  • 42
  • I dont see much difference here than in the proxy. Here you have ` if (some expression){' this is the too much info ... return this or that bean. In the proxy too the worker beans could be injected from spring and only the business logic would determine to call method on bean a or b – tgkprog Jun 17 '14 at 20:27
  • 2
    @Stefan How to add `replaced-method` tag when creating bean using Annotation `@Bean` – Piyush Feb 03 '15 at 07:22
5

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.

Tomáš Záluský
  • 10,735
  • 2
  • 36
  • 64
3

There are ways for manipulating spring context before that created.

  1. A way is, you use GenericApplicationContext and GenericBeanDefinition classes for manipulate context. Following sample code shown this solution :

    GenericApplicationContext context = new GenericApplicationContext();
    
    XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(context);
    xmlReader.loadBeanDefinitions(new ClassPathResource(original-context));
    BeanDefinitionRegistry registry = ((BeanDefinitionRegistry) context);
    
    GenericBeanDefinition myBean = new GenericBeanDefinition();
    myBean.setBeanClass(MyCustomClass.class);
    myBean.getPropertyValues().add("name", "My-Name");
    registry.registerBeanDefinition("my_bean_name", myBean);
    
    context.refresh();
    

by this snippet code you can add or remove or change beans befor it created.

  1. Second solution is using BeanPostProcessor mechanism in spring. For detailes see this url : http://www.roseindia.net/tutorial/spring/spring3/ioc/beanpostprocessor.html or Why is this BeanPostProcessor needed in addition to a UserDetailsService in this Spring 3.0 authentication example?
Community
  • 1
  • 1
Sam
  • 6,770
  • 7
  • 50
  • 91
0

Another easy approach can be using Atomic Reference of your class as a Bean instead of using the class directly. Then you can inject that Atomic Reference anywhere and update it. All other services will use the latest version of your class after you update it in atomic reference. And I think it makes sense.

Read this for atomic reference usage: https://stackoverflow.com/a/24765321/5197662

One disadvantage of this approach is loosing that spring can not process annotations of your class. For example, u cant use @Cachable, @Async, @PreDestroy and others. But the solution is good when u just need an object and it properties.

Sepehr GH
  • 1,297
  • 1
  • 17
  • 39
0

You're crossing a fine line here. Basically you are trying to put application logic in the Spring container. Avoid programming with your configuration files and use Spring (or whatever DI framework) only for basic wiring.

The proxy suggestion @mrembisz makes is therefor the preferred one. That way application logic and configuration are separated.

M Platvoet
  • 1,679
  • 1
  • 10
  • 14