Home > Back-end >  Injection interface with multiple implementation
Injection interface with multiple implementation

Time:08-01

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/

  • Related