54

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...

sawan
  • 2,341
  • 3
  • 25
  • 51
user666
  • 1,750
  • 3
  • 18
  • 34
  • 1
    The same way as in Spring (hint: Spring Boot is in fact Spring): you define a bean either using an annotation, or using a Bean-annotated method, as explained in the Spring documentation, and you autowire the interface that this bean implements. If you showed code rather than vaguely describing it, everything would be easier. – JB Nizet Aug 09 '18 at 11:48
  • For example, if we have an interface called ChargeInterface and it has two implementations: ChargeInDollars and ChrageInEuro and you have another class containing a certain business logic called AmericanStoreManager that should use the ChargeInDollars implementation of ChargeInterface. You define an autowired ChargeInterface but how you decide what implementation to use? – user666 Aug 09 '18 at 12:16
  • 1
    Using qualifiers, exactly the same way as in Spring, because Spring-boot is Spring. So, read the Spring documentation, and look for "Qualifier". Or, since you want a specific implementation anyway, you can simply autowire the class, and not the interface. – JB Nizet Aug 09 '18 at 12:18

7 Answers7

91

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

tsarenkotxt
  • 3,231
  • 4
  • 22
  • 38
  • For Unit test i used the following annotations: @SpringBootTest(classes=CalendarUtil.class) @RunWith(SpringRunner.class) and then i autowired the class. – user666 Aug 10 '18 at 05:38
  • Do you no longer have problems with `@Autowired ` ? – tsarenkotxt Aug 10 '18 at 14:10
  • yes when we add the spring boot test and run with annotations, we can use the autowired annotation successfully – user666 Aug 13 '18 at 11:15
  • Whenever i use the above in Junit alongside Mocking, the autowired failed again. Any advice on this? The problem is that i dont want to mock all the classes i want some to be mocked and others to be autowired. How can i achieve this? – user666 Aug 29 '18 at 06:19
  • 1
    I managed to do this using @Spy instead of Autowired as annotation in Junit and to initialize the class myself without using Spring run annotations. – user666 Aug 29 '18 at 06:48
  • try `SpyBean` and also with `@RunWith(SpringRunner.class)`, have a look at this example - https://shekhargulati.com/2017/07/20/using-spring-boot-spybean/ – tsarenkotxt Aug 29 '18 at 20:02
  • When i tried to inject two implementations in one bean in different field it didnt help. The injection with the name of the implementation did the trick. – S.Daineko Apr 29 '20 at 11:50
  • @S.Dayneko could you provide some code example/ Spring version ? do you use `@Qualifier("implOne")` & `@Qualifier("implTwo")`, also it can be qualified by filed name without `@Qualifier` annotation – tsarenkotxt Apr 29 '20 at 14:51
  • See Raj Shah answer below. It also work with constructor(and without @Autowired since spring 4.3) – S.Daineko Apr 30 '20 at 09:57
  • right, as I wrote above - `... it can be qualified by filed name ...` – tsarenkotxt Apr 30 '20 at 13:05
46

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

Eg:

@Autowired
MyService firstService;

@Autowired
MyService secondService;
Raj Shah
  • 766
  • 7
  • 15
12

Assume that you have a GreetingService

public interface GreetingService {
    void doGreetings();
}

And you have 2 implementations HelloService

@Service
@Slf4j
public class HelloService implements GreetingService{
    @Override
    public void doGreetings() {
        log.info("Hello world!");       
    }
}

and HiService

@Slf4j
@Service
public class HiService implements GreetingService{
    @Override
    public void doGreetings() {
        log.info("Hi world!");
    }
}

Then you have another interface, which is BusinessService to call some business

public interface BusinessService {
    void doGreetings();
}

There are some ways to do that

#1. Use @Autowired

@Component
public class BusinessServiceImpl implements BusinessService{
    
    @Autowired
    private GreetingService hiService; // Spring automatically maps the name for you, if you don't want to change it.
    
    @Autowired
    private GreetingService helloService;
    

    @Override
    public void doGreetings() {
        hiService.doGreetings();
        helloService.doGreetings();
    }

}

In case you need to change your implementation bean name, refer to other answers, by setting the name to your bean, for example @Service("myCustomName") and applying @Qualifier("myCustomName")

#2. You can also use constructor injection

@Component
public class BusinessServiceImpl implements BusinessService {

    private final GreetingService hiService;

    private final GreetingService helloService;

    public BusinessServiceImpl(GreetingService hiService, GreetingService helloService) {
        this.hiService = hiService;
        this.helloService = helloService;
    }

    @Override
    public void doGreetings() {
        hiService.doGreetings();
        helloService.doGreetings();
    }

}

This can be

public BusinessServiceImpl(@Qualifier("hiService") GreetingService hiService, @Qualifier("helloService") GreetingService helloService)

But I am using Spring Boot 2.6.5 and

public BusinessServiceImpl(GreetingService hiService, GreetingService helloService)

is working fine, since Spring automatically get the names for us.

#3. You can also use Map for this

@Component
@RequiredArgsConstructor
public class BusinessServiceImpl implements BusinessService {

    private final Map<String, GreetingService> servicesMap; // Spring automatically get the bean name as key

    @Override
    public void doGreetings() {
        servicesMap.get("hiService").doGreetings();
        servicesMap.get("helloService").doGreetings();
    }

}

List also works fine if you run all the services. But there is a case that you want to get some specific implementation, you need to define a name for it or something like that. My reference is here

For this one, I use @RequiredArgsConstructor from Lombok.

Tuan Hoang
  • 586
  • 1
  • 7
  • 14
5

There are 2 approaches when we have autowiring of an interface with multiple implementations:

  1. Spring @Primary annotation

In short it tells to our Spring application whenever we try to autowire our interface to use that specific implementation which is marked with the @Primary annotation. It is like a default autowiring setting. It can be used only once per cluster of implementations of an interface. → @Primary Docs

  1. Spring @Qualifier annotation

This Spring annotation is giving us more control to select the exact implementation wherever we define a reference to our interface choosing among its options. → @Qualifier Docs

For more details follow the links to their documentation.

nenito
  • 1,214
  • 6
  • 19
  • 33
4

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....
    }
}
Urosh T.
  • 3,336
  • 5
  • 34
  • 42
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);        
        
        
    }   
}
Wundwin Born
  • 3,467
  • 19
  • 37
Jimmy
  • 995
  • 9
  • 18
2
public interface SomeInterfaces {

    void send(String message);

    String getType();
}
  • kafka-service
@Component
public class SomeInterfacesKafkaImpl implements SomeInterfaces {

    private final String type = "kafka";

    @Override
    public void send(String message) {
        System.out.println(message + "through Kafka");
    }

    @Override
    public String getType() {
        return this.type;
    }
}
  • redis-service
@Component
public class SomeInterfacesRedisImpl implements SomeInterfaces {

    private final String type = "redis";

    @Override
    public void send(String message) {
        System.out.println(message + "through Redis");
    }

    @Override
    public String getType() {
        return this.type;
    }
}
  • master
@Component
public class SomeInterfacesMaster {

    private final Set<SomeInterfaces> someInterfaces;


    public SomeInterfacesMaster(Set<SomeInterfaces> someInterfaces) {
        this.someInterfaces = someInterfaces;
    }

    public void sendMaster(String type){

        Optional<SomeInterfaces> service =
                someInterfaces
                        .stream()
                        .filter(service ->
                                service.getType().equals(type)
                        )
                        .findFirst();
        SomeInterfaces someService =
                service
                        .orElseThrow(() -> new RuntimeException("There is not such way for sending messages."));

        someService .send(" Hello. It is a letter to ....");

    }
}
  • test

@SpringBootTest
public class MultiImplementation {
}

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeInterfacesMasterTest extends MultiImplementation {

    @Autowired
    private SomeInterfacesMaster someInterfacesMaster;

    @Test
    void sendMaster() {
        someInterfacesMaster.sendMaster("kafka");
    }
}

Thus, according to the Open/Closed principle, we only need to add an implementation without breaking existing code.

@Component
public class SomeInterfacesRabbitImpl implements SomeInterfaces {

   private final String type = "rabbit";

    @Override
    public void send(String message) {
        System.out.println(message + "through Rabbit");
    }

    @Override
    public String getType() {
        return this.type;
    }
}

  • test-v2
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeInterfacesMasterTestV2 extends MultiImplementation {

    @Autowired
    private SomeInterfacesMaster someInterfacesMaster;


    @Test
    void sendMasterV2() {
        someInterfacesMaster.sendMaster("rabbit");
    }
}
skyho
  • 1,438
  • 2
  • 20
  • 47