My service has a @Controller
with multiple APIs.
Each API accepts a specific kind of object.
I would like to inject a single interface into a controller class, but have different implementations of the interface depending on the type of the input argument - is that possible?
@Controller
public class ApiClass{
private final Service service;
public ApiClass(Service service) {
this.service = service;
}
public ResponseEntity<Response> apiFirst (Object1 object1) {
return ResponseEntity.ok(service.process(object1));
}
public ResponseEntity<Response> apiTwo (Object2 object2) {
return ResponseEntity.ok(service.process(object2));
}
}
public interface Service <T extends OwnObjectClass>{
void process (T object);
}
public class Implementation1 implements Service {
@Override
void process (Object1 object) {
--some code;
}
}
public class Implementation2 implements Service {
@Override
void process (Object2 object) {
--some code;
}
}
How to do it correctly so that for each implementation not to add a new injection to the ApiClass
?
CodePudding user response:
Spring will provide the primary bean for the interface implementation unless you use the @Qualifer
annotation with the desired instance. The infected bean can not mutate to another instance.
If you don't want to use multiple injections in the controller, you can create a ServiceProvider
and ask for a specific implementation each time.
Here is an example:
public class ApiClass{
private final ServiceProvider provider;
public ApiClass(ServiceProvider provider) {
this.provider = provider;
}
public ResponseEntity<Response> apiFirst (Object1 object1) {
return ResponseEntity.ok(provider.getService("Implementation1").process(object1));
}
public ResponseEntity<Response> apiTwo (Object2 object2) {
return ResponseEntity.ok(provider.getService("Implementation2").process(object1));
}
}
@org.springframework.stereotype.Service
public class ServiceProvider {
private Map<String, Service> services;
public ServiceProvider(List<Service> services) {
this.services = services.stream()
.collect(java.util.stream.Collectors.toMap(
Service::type,
service -> service
)
);
}
public Service getService(String type) {
return services.get(type);
}
}
interface Service<T extends OwnObjectClass> {
String type();
void process(T object);
}
@org.springframework.stereotype.Service("Implementation1")
class Implementation1 implements Service {
@Override
public String type() {
return "Implementation1";
}
@Override
public void process(OwnObjectClass object) {
}
}
@org.springframework.stereotype.Service("Implementation2")
class Implementation2 implements Service {
@Override
public String type() {
return "Implementation2";
}
@Override
public void process(OwnObjectClass object) {
}
}
You can change the string in the type for an Enum.
CodePudding user response:
There is another way using HandlerMethodArgumentResolver
where you can inject your dependency directly into the method definition.
Here is a nice article explaining it: https://reflectoring.io/spring-boot-argumentresolver/