Spring boot autowiring an interface with multiple implementations

76,997

Solution 1

Use @Qualifier annotation is used to differentiate beans of the same interface
Take look at Spring Boot documentation
Also, to inject all beans of the same interface, just autowire List of interface
(The same way in Spring / Spring Boot / SpringBootTest)
Example below:

@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
}

public interface MyService {

    void doWork();

}

@Service
@Qualifier("firstService")
public static class FirstServiceImpl implements MyService {

    @Override
    public void doWork() {
        System.out.println("firstService work");
    }

}

@Service
@Qualifier("secondService")
public static class SecondServiceImpl implements MyService {

    @Override
    public void doWork() {
        System.out.println("secondService work");
    }

}

@Component
public static class FirstManager {

    private final MyService myService;

    @Autowired // inject FirstServiceImpl
    public FirstManager(@Qualifier("firstService") MyService myService) {
        this.myService = myService;
    }

    @PostConstruct
    public void startWork() {
        System.out.println("firstManager start work");
        myService.doWork();
    }

}

@Component
public static class SecondManager {

    private final List<MyService> myServices;

    @Autowired // inject MyService all implementations
    public SecondManager(List<MyService> myServices) {
        this.myServices = myServices;
    }

    @PostConstruct
    public void startWork() {
        System.out.println("secondManager start work");
        myServices.forEach(MyService::doWork);
    }

}

}

For the second part of your question, take look at this useful answers first / second

Solution 2

You can also make it work by giving it the name of the implementation.

Eg:

@Autowired
MyService firstService;

@Autowired
MyService secondService;

Solution 3

As mentioned in the comments, by using the @Qualifier annotation, you can distinguish different implementations as described in the docs.

For testing, you can use also do the same. For example:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class MyClassTests {

        @Autowired
        private MyClass testClass;
        @MockBean
        @Qualifier("default")
        private MyImplementation defaultImpl;

        @Test
        public void givenMultipleImpl_whenAutowiring_thenReturnDefaultImpl() {
    // your test here....
    }
}

Solution 4

If we have multiple implementations of the same interface, Spring needs to know which one it should be autowired into a class. Here is a simple example of validator for mobile number and email address of Employee:-

Employee Class:

public class Employee {

 private String mobileNumber;
 private String emailAddress;
 
 ...
 
 /** Getters & Setters omitted **/

}

Interface EmployeeValidator:

public interface EmployeeValidator {
    public Employee validate(Employee employee);
}

First implementation class for Mobile Number Validator:

@Component(value="EmployeeMobileValidator")
public class EmployeeMobileValidator implements EmployeeValidator {
    @Override
    public Employee validate(Employee employee) {
        //Mobile number Validation logic goes here.
    }
}

Second implementation class for Email address Validator:

@Component(value="EmployeeEmailValidator")
public class EmployeeEmailValidator implements EmployeeValidator {
    @Override
    public Employee validate(Employee employee) {
        //Email address validation logic goes here.
    }
}

We can now autowired these above validators individually into a class.

Employee Service Interface:

public interface EmployeeService {
    public void handleEmployee(Employee employee);
}

Employee Service Implementation Class

@Service
public class EmployeeServiceImpl implements EmployeeService {
    
    /** Autowire validators individually **/
    
    @Autowired
    @Qualifier("EmployeeMobileValidator")    // Autowired using qualifier for mobile validator
    private EmployeeValidator mobileValidator;

    @Autowired
    @Qualifier("EmployeeEmailValidator")    // Autowired using qualifier for email valodator
    private EmployeeValidator emailValidator;

    @Override
    public void handleEmployee(Employee employee) {
    
        /**You can use just one instance if you need**/
        
        employee = mobileValidator.validate(employee);        
        
        
    }   
}
Share:
76,997
user666
Author by

user666

Updated on July 09, 2022

Comments

  • user666
    user666 almost 2 years

    In normal Spring, when we want to autowire an interface, we define it's implementation in Spring context file.

    1. What about Spring boot?
    2. how can we achieve this?

    currently we only autowire classes that are not interfaces.

    Another part of this question is about using a class in a Junit class inside a Spring boot project.

    If we want to use a CalendarUtil for example, if we autowire CalendarUtil, it will throw a null pointer exception. What can we do in this case? I just initialized using "new" for now...