Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
186 views
in Technique[技术] by (71.8m points)

java - Inject spring bean dynamically

In a java-spring web-app I would like to be able to dynamically inject beans. For example I have an interface with 2 different implementations:

enter image description here

In my app I'm using some properties file to configure injections:

#Determines the interface type the app uses. Possible values: implA, implB
myinterface.type=implA

My injections actually loaded conditionally relaying on the properties values in the properties file. For example in this case myinterface.type=implA wherever I inject MyInterface the implementation that will be injected will be ImplA (I accomplished that by extending the Conditional annotation).

I would like that during runtime - once the properties are changed the following will happen (without server restart):

  1. The right implementation will be injected. For example when setting myinterface.type=implB ImplB will be injected where-ever MyInterface is used
  2. Spring Environment should be refreshed with the new values and re-injected as well to beans.

I thought of refreshing my context but that creates problems. I thought maybe to use setters for injection and re-use those setters once properties are re-configured. Is there a working practice for such a requirement?

Any ideas?

UPDATE

As some suggested I can use a factory/registry that holds both implementations (ImplA and ImplB) and returns the right one by querying the relevant property. If I do that I still have the second challenge - the environment. for example if my registry looks like this:

@Service
public class MyRegistry {

private String configurationValue;
private final MyInterface implA;
private final MyInterface implB;

@Inject
public MyRegistry(Environmant env, MyInterface implA, MyInterface ImplB) {
        this.implA = implA;
        this.implB = implB;
        this.configurationValue = env.getProperty("myinterface.type");
}

public MyInterface getMyInterface() {
        switch(configurationValue) {
        case "implA":
                return implA;
        case "implB":
                return implB;
        }
}
}

Once property has changed I should re-inject my environment. any suggestions for that?

I know I can query that env inside the method instead of constructor but this is a performance reduction and also I would like to think of an ider for re-injecting environment (again, maybe using a setter injection?).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I would keep this task as simple as possible. Instead of conditionally load one implementation of the MyInterface interface at startup and then fire an event that triggers dynamic loading of another implementation of the same interface, I would tackle this problem in a different way, that is much simpler to implement and maintain.

First of all, I'd just load all possible implementations:

@Component
public class MyInterfaceImplementationsHolder {

    @Autowired
    private Map<String, MyInterface> implementations;

    public MyInterface get(String impl) {
        return this.implementations.get(impl);
    }
}

This bean is just a holder for all implementations of the MyInterface interface. Nothing magic here, just common Spring autowiring behavior.

Now, wherever you need to inject a specific implementation of MyInterface, you could do it with the help of an interface:

public interface MyInterfaceReloader {

    void changeImplementation(MyInterface impl);
}

Then, for every class that needs to be notified of a change of the implementation, just make it implement the MyInterfaceReloader interface. For instance:

@Component
public class SomeBean implements MyInterfaceReloader {

    // Do not autowire
    private MyInterface myInterface;

    @Override
    public void changeImplementation(MyInterface impl) {
        this.myInterface = impl;
    }
}

Finally, you need a bean that actually changes the implementation in every bean that has MyInterface as an attribute:

@Component
public class MyInterfaceImplementationUpdater {

    @Autowired
    private Map<String, MyInterfaceReloader> reloaders;

    @Autowired
    private MyInterfaceImplementationsHolder holder;

    public void updateImplementations(String implBeanName) {
        this.reloaders.forEach((k, v) -> 
            v.changeImplementation(this.holder.get(implBeanName)));
    }
}

This simply autowires all beans that implement the MyInterfaceReloader interface and updates each one of them with the new implementation, which is retrieved from the holder and passed as an argument. Again, common Spring autowiring rules.

Whenever you want the implementation to be changed, you should just invoke the updateImplementations method with the name of the bean of the new implementation, which is the lower camel case simple name of the class, i.e. myImplA or myImplB for classes MyImplA and MyImplB.

You should also invoke this method at startup, so that an initial implementation is set on every bean that implements the MyInterfaceReloader interface.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...