I have two (more in the future) implementations of ImportantService
– VeryImportantService
and LessImportantService
:
public interface ImportantService<T extends ImportantRequest> {}
@Service
public class VeryImportantService implements ImportantService<VeryImportantRequest> {}
@Service
public class LessImportantService implements ImportantService<LessImportantRequest> {}
And then I have a controller, in which I want to inject all of the implementations of ImportantService
:
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/important")
public class ImportantController<T extends ImportantRequest> {
private final ImportantService<T> importantService;
@PostMapping
public ResponseEntity<ImportantResponse> create(@RequestBody @Valid T request) {
// very important code here
}
}
Obviously, such king of injecting fails:
UnsatisfiedDependencyException: Error creating bean with name 'importantController' defined in file ...
...
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
What I want is:
Inject all of the implementations of ImportantService
, and then, based on the T
automatically select required bean. I know I can add method to ImportantService
, which returns the type that implementation works with and then inject ImportantService
as List<ImportantService> importantServices
and then filter like this:
importantServices.stream()
.filter(importantService -> importantService.getType().equals(request.getClass()))
.findFirst()
.ifPresent(importantService -> importantService.doImportantJob(request));
BUT! I have hundreds of services to refactor like this and I really don't want to write additional logic to controllers.
I know about @Conditional
annotation and Condition
interface, but AFAIK there's no way to make them do what I want.
CodePudding user response:
Why not implement the proxy pattern?
example:
@Service
@Primary
@RequiredArgsConstructor
public class ImportantServiceProxy implements ImportantService<T extends ImportantRequest> {
private final List<ImportantService> importantServices;
private ImportantService getImportantService(ImportantRequest request){
return this.importantServices.stream()
.filter(importantService -> importantService.getType().equals(request.getClass()))
.findFirst()
.get();
}
public void doImportantJob(ImportantRequest request){
this.getImportantService(request).doImportantJob(request);
}
}
Then in your controller you can call the function without check the type.
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/important")
public class ImportantController<T extends ImportantRequest> {
private final ImportantService<T> importantService;
@PostMapping
public ResponseEntity<ImportantResponse> create(@RequestBody @Valid T request) {
importantService.doImportantJob(request);
}
}
CodePudding user response:
what you want is a list of beans which are of type ImportantService so you have to declare a variable like this.
final List<ImportantService> importantServices;
demoController(List<ImportantService> importantServices) {
this.importantServices = importantServices;
}