Reputation: 3560
This question is intended to make an answer for a useful issue.
Suppose we have a Spring application with a @Controller, an interface and different implementations of that interface.
We want that the @Controller use the interface with the proper implementation, based on the request that we receive.
Here is the @Controller:
@Controller
public class SampleController {
@RequestMapping(path = "/path/{service}", method = RequestMethod.GET)
public void method(@PathVariable("service") String service){
// here we have to use the right implementation of the interface
}
}
Here is the interface:
public interface SampleInterface {
public void sampleMethod(); // a sample method
}
Here is one of the possibile implementation:
public class SampleInterfaceImpl implements SampleInterface {
public void sampleMethod() {
// ...
}
}
And here is another one:
Here is one of the possibile implementation:
public class SampleInterfaceOtherImpl implements SampleInterface {
public void sampleMethod() {
// ...
}
}
Below I'll show the solution that I've found to use one of the implementations dynamically based on the request.
Upvotes: 1
Views: 3258
Reputation: 13181
I'm not convinced with your solution because there's an implicit link between an HTTP parameter value and a bean qualifier. Innocent change of the bean name would result in a disaster that could be tricky to debug. I would encapsulate all the necessary information in one place to ensure any changes only need to be done in a single bean:
@Controller
public class SampleController {
@Autowired
private SampleInterfaceImpl basic;
@Autowired
private SampleInterfaceOtherImpl other;
Map<String, SampleInterface> services;
@PostConstruct
void init() {
services = new HashMap()<>;
services.put("Basic", basic);
services.put("Other", other);
}
@RequestMapping(path = "/path/{service}", method = RequestMethod.GET)
public void method(@PathVariable("service") String service){
SampleInterface sample = services.get(service);
// remember to handle the case where there's no corresponding service
sample.sampleMethod();
}
}
Also, dependency on the ApplicationContext
object will make it more complicated to test.
NB. to make it more robust I'd use enums instead of the "Basic" and "Other" strings.
However, if you know you'll only have two types of the service to choose from, this would be the "keep it simple stupid" way:
@Controller
public class SampleController {
@Autowired
private SampleInterfaceImpl basic;
@Autowired
private SampleInterfaceOtherImpl other;
@RequestMapping(path = "/path/Basic", method = RequestMethod.GET)
public void basic() {
basic.sampleMethod();
}
@RequestMapping(path = "/path/Other", method = RequestMethod.GET)
public void other() {
other.sampleMethod();
}
}
Upvotes: 0
Reputation: 113
Honestly I don't think the idea of exposing internal implementation details in the URL just to avoid writing some lines of code is good. The solution proposed by @kriger at least adds one indirection step using a key / value approach.
I would prefer to create a Factory Bean (to be even more enterprise oriented even an Abstract Factory Pattern) that will choose which concrete implementation to use. In this way you will be able to choose the interface in a separate place (the factory method) using any custom logic you wish. And you will be able to decouple the service URL from the concrete implementation (which is not very safe).
If you are creating a very simple service your solution will work, but in an enterprise environment the use of patterns is vital to ensure maintenability and scalability.
Upvotes: 0
Reputation: 10075
I solved that problem like this:
supports(...)
and inject a List<SampleInterface>
into your controller.getCurrentImpl(...)
in the controller to resolve it with the help of supports
Ordered
interface or use the annotation @Order
.This way you have no need for using the ApplicationContext explicitly.
Upvotes: 0
Reputation: 3560
The solution I've found is this one.
First, we have to autowire the ApplicationContext in the @Controller.
@Autowired
private ApplicationContext appContext;
Second, we have to use the @Service annotation in the implementations of the interface. In the example, I give them the names "Basic" and "Other".
@Service("Basic")
public class SampleInterfaceImpl implements SampleInterface {
public void sampleMethod() {
// ...
}
}
@Service("Other")
public class SampleInterfaceOtherImpl implements SampleInterface {
public void sampleMethod() {
// ...
}
}
Next, we have to obtain the implementation in the @Controller. Here's one possible way:
@Controller
public class SampleController {
@Autowired
private ApplicationContext appContext;
@RequestMapping(path = "/path/{service}", method = RequestMethod.GET)
public void method(@PathVariable("service") String service){
SampleInterface sample = appContext.getBean(service, SampleInterface.class);
sample.sampleMethod();
}
}
In this way, Spring injects the right bean in a dynamic context, so the interface is resolved with the properly inmplementation.
Upvotes: 4