Home > database >  Inject multiple beans of the same type and automatically select between them based on generic type
Inject multiple beans of the same type and automatically select between them based on generic type

Time:03-20

I have two (more in the future) implementations of ImportantServiceVeryImportantService 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;
}
  • Related