8

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.

Marco Liceti
  • 167
  • 2
  • 10
  • You could pass a class instead of a string in your method, like `Foo.class`. – Turtle May 16 '17 at 11:53
  • @Nathan I'm not sure about what you're proposing. Note that choosing the right class is the problem, not the solution. – Marco Liceti May 16 '17 at 11:59
  • Does it have to be a lookup by key? If your solution is just a way around the problem to distinguish between implementations of the same interface, then autowiring with `@Qualifier` and reference the beans by name might help you. – msp May 16 '17 at 12:09
  • @msparer The goal is to be able to turn some kind of input into a suitable implementation. In my examples I used a string called `someKey` which was treated as a bean name, but this is not necessarily the case. It could even be a number, and the implementation may be chosen based on the size of this number, with some implementation being more efficient on small numbers and some other on bigger numbers. Also, `@Qualifier` doesn't help, since it lets you choose a particular bean but once it has been wired you cannot switch to another :-/ – Marco Liceti May 16 '17 at 13:39
  • seems that the issue is similar with [this](https://stackoverflow.com/a/40121702/6825250) – Sergey Bespalov Dec 05 '18 at 09:09
  • Possible duplicate of [Inject spring bean dynamically](https://stackoverflow.com/questions/40105044/inject-spring-bean-dynamically) – Sergey Bespalov Dec 05 '18 at 09:24

3 Answers3

6

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

pvpkiran
  • 25,582
  • 8
  • 87
  • 134
  • And if you want the key to be something else other than the bean name, you can use @autowire on a setter and create the key based on one of the bean properties. – Tomer A May 16 '17 at 12:20
  • Mapping strings to bean names is fine for simple scenarios, but more generally the logic required to choose the right (or even the _best_) implementation could be complex. Of course this logic should be encapsulated inside a dedicated object (say, the `class MyHelper`). And of course this object can (and sometimes should) collaborate with other objects (which is basically what you are suggesting with the `enum Mapper` and the `class ImplFactory`). But these are just implementation details to me. My actual goal is to make the framework more aware of what I'm trying to do. – Marco Liceti May 16 '17 at 15:08
3

The standard way of doing what you want should be this:

interface YourInterface {
    void doSomething();
}

public class YourClass {

    @Inject @Any Instance<YourInterface> anImplementation;

    public void yourMethod(String someInput) {
        Annotation qualifier = turnInputIntoQualifier(someInput);
        anImplementation.select(qualifier).get().doSomething();
    }

    private Annotation turnInputIntoQualifier(String input) {
        ...
    }

}

Currently, however Spring does not support it (although it's planned for v5.x). It should work on application servers.

If you want to stick with Spring, the ServiceLocatorFactoryBean based solution is probably the best one.

Elliot
  • 46
  • 1
0

You could name your beans when you declare them and your helper can ask application context to return the bean of given type. Based on the scope declared on the bean, application context can create a new instance if you need or reuse singleton or other scopes avaialable based on context. This way you can fully utilize spring functionality.

For example

@Service
public class MyHelper {
   @Autowired
   ApplicationContext applicationContext;

   public AnInterface giveMeTheRightImplementation(String key) {

   return context.getBean(key);
}

}

@Service("foo")
public class Foo implements AnInterface {
}
user871199
  • 1,420
  • 19
  • 28