Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring: how to change interface implementations at runtime

As a Java developer I frequently need to choose between different implementations of my interfaces. Sometimes this choice can be done once, while some other times I need different implementations in response to the different inputs that my program receives. In other words, I need to be able to change implementation at runtime. This is easily achievable through an helper object that converts some key (based on user input) into a reference to a suitable interface implementation.

With Spring I can design such an object as a bean and inject it wherever I need:

public class MyClass {

    @Autowired
    private MyHelper helper;

    public void someMethod(String someKey) {
        AnInterface i = helper.giveMeTheRightImplementation(someKey);
        i.doYourjob();
    }

}

Now, how should I implement the helper? Let's start with this:

@Service
public class MyHelper {

    public AnInterface giveMeTheRightImplementation(String key) {
        if (key.equals("foo")) return new Foo();
        else if (key.equals("bar")) return new Bar();
        else ...
    }

}

Such a solution has several flaws. One of the worst is the fact that instances returned from the helper are unknown to the container and thus cannot benefit from dependency injection. In other words, even if I define the Foo class like this:

@Service
public class Foo {

    @Autowired
    private VeryCoolService coolService;

    ...

}

...instances of Foo returned by MyHelper won't have the coolService field properly initialized.

To avoid this, a frequently suggested workaround is to inject each possible implementation inside the helper:

@Service
public class MyHelper {

    @Autowired
    private Foo foo;

    @Autowired
    private Bar bar;

    ...

    public AnInterface giveMeTheRightImplementation(String key) {
        if (key.equals("foo")) return foo;
        else if (key.equals("bar")) return bar;
        else ...
    }

}

But I'm not a big fan of such solutions. I find more elegant and maintainable something like this:

@Service
public class MyHelper {

    @Autowired
    private ApplicationContext app;

    public AnInterface giveMeTheRightImplementation(String key) {
        return (AnInterface) app.getBean(key);
    }

}

This is based on Spring's ApplicationContext.

A similar solution is to use the ServiceLocatorFactoryBean class:

public interface MyHelper {

    AnInterface giveMeTheRightImplementation(String key);

}

// Somewhere else, in Java config

@Bean
ServiceLocatorFactoryBean myHelper() {
    ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
    bean.setServiceLocatorInterface(MyHelper.class);
    return bean;
}

But since I'm not a Spring expert I wonder if there are even better approaches.

like image 323
Marco Liceti Avatar asked May 16 '17 11:05

Marco Liceti


People also ask

How do I change the Spring properties in runtime?

To change properties in a file during runtime, we should place that file somewhere outside the jar. Then we tell Spring where it is with the command-line parameter –spring. config. location=file://{path to file}.

Can I inject a interface in Spring?

Save this answer. Show activity on this post. DI exists in two major variants, Constructor-based dependency injection and Setter-based dependency injection. Also see Interface injection is not implemented in Spring clearly states it.


1 Answers

I follow this approach in my projects. This is not foolproof, but it serves quite well in terms of adding new implementations with very less configuration code.

I create a enum with something like this

enum Mapper{
    KEY1("key1", "foo"),
    KEY2("key2", "bar")
    ;

    private String key;
    private String beanName;

    public static getBeanNameForKey(String key){
       // traverse through enums using values() and return beanName for key
    }
}

Let's assume both Foo and Bar implement from a comman Interface. Let's call It AnInterface

class ImplFactory{

    @Autowired
    Map<String, AnInterface> implMap; // This will autowire all the implementations of AnInterface with the bean name as the key

    public AnInterface getImpl(string beanName){
            implMap.get(beanName);
    }
  }

And your helper class will look like this

@Service
public class MyHelper {

@Autowired
ImplFactory factory;

    public AnInterface giveMeTheRightImplementation(String key) {

        String beanName = Mapper.getBeanNameForKey(key);  
        factory.getImpl(beanName);
    }  
}

Some of the advantages of this approach is,
1. It avoids the lengthy if else's or switch cases in selecting right implementation.
2. If you wish to add a new implementation. All you have got to do is add a Mapper in your enum(apart from adding your new Impl class).
3. You can even configure the Bean Names for your impl classes which you want(if you dont want the default bean names given by spring). This names will be the key for the map in your factory class. Which you have to use in your enum.

EDIT: If you wish to give a custom name to your beans, you can use the value attribute of one of the stereotype annotations. For example. If you have annotated your Impl as @Component or @Service, then do @Component("myBeanName1") or @Service("myBeanName2")

like image 158
pvpkiran Avatar answered Sep 18 '22 10:09

pvpkiran