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.