Home > Software engineering >  How to automatically inject implementation based on Generics
How to automatically inject implementation based on Generics

Time:06-03

I have a Service class defined like this,

@RequiredArgsConstructor
class SomeService<T extends AbstractResponse> {
    private final ValidationService<T> validationService;
    ....
}

And I have two kinds of AbstractResponse, ResponseA and ResponseB and have a validation service defined for both of them.

@Service("aValidationService");
class AValidationService<ResponseA> implements ValidationService<ResponseA> {
    ....
}

and

@Service("ValidationService");
class BValidationService<ResponseB> implements ValidationService<ResponseB> {
    ....
}

Right now spring is throwing an error because it's not able to deduce the implementation of ValidationService to use in SomeService as there are two implementations of it. How do I make spring deduce the correct implementation based on the type of AbstractResponse?

CodePudding user response:

Hope that I understood your requirements.

You can not automatically inject, when you have (2) of the same kind. In this case ValidationService.

You could inject @ValidationServiceA, or @ValidationServiceB, or a List<ValidationServiceI> and then return the one you want based on a <T> type you care about:

The solution below highlights that.

The method getGenericParameter() is used to return the <T> parameter. This is to avoid the use of Reflection.

The method methodWhichDeterminesWhichServiceToUseBasedOnResponseType to used to determine which ValidationService to use based on the input that you require.

You can find the complete solution below, including a verification Test.

import org.springframework.stereotype.Service;

@Service
public class ValidationServiceA implements ValidationServiceI<ResponseA>{

    @Override public Class<ResponseA> getGenericParameter() {
        return ResponseA.class;
    }

    public void print(){
        System.out.println("Service A");
    }
}

   @Service
public class ValidationServiceB implements ValidationServiceI<ResponseB>{

    @Override public Class<ResponseB> getGenericParameter() {
        return ResponseB.class;
    }


    public void print(){
        System.out.println("Service B");
    }

}

    public interface ValidationServiceI<T>{
    
        Class<T> getGenericParameter();
    
        void print();
    
    }

@Service
public class ServiceWhichCallsOthers {

    @Autowired
    private List<ValidationServiceI> validationServices;

    public <T> ValidationServiceI<T> methodWhichDeterminesWhichServiceToUseBasedOnResponseType(T responseType){

        Optional<ValidationServiceI> validationServiceSupportingResponse = validationServices.stream().filter(validationServiceI -> validationServiceI.getGenericParameter().equals(responseType)).findFirst();

        return validationServiceSupportingResponse.get();
    }

    public void callValidationServiceA(){
        methodWhichDeterminesWhichServiceToUseBasedOnResponseType(ResponseA.class).print();
    }

    public void callValidationServiceB(){
        methodWhichDeterminesWhichServiceToUseBasedOnResponseType(ResponseB.class).print();
    }

}

@SpringBootTest
public class ServiceWhichCallsOthersIT {

    @Autowired
    private ServiceWhichCallsOthers serviceWhichCallsOthers;

    @Test
    public void validateBasedOnResponseType(){

        Assertions.assertEquals(ValidationServiceA.class, serviceWhichCallsOthers.methodWhichDeterminesWhichServiceToUseBasedOnResponseType(ResponseA.class).getClass());
        Assertions.assertEquals(ValidationServiceB.class, serviceWhichCallsOthers.methodWhichDeterminesWhichServiceToUseBasedOnResponseType(ResponseB.class).getClass());


        serviceWhichCallsOthers.callValidationServiceA();
        serviceWhichCallsOthers.callValidationServiceB();

    }
}
  • Related