Introduction
In a previous article we have explored what is Dependency Injection in Spring Boot and the different ways in which we can inject dependencies into our application. For illustration, we have seen how a HeroService is injected into our Controller HeroController and how the endpoint gets data by called the getHeroes() method from the HeroService.
Let’s say the HeroService is also a Spring Bean, which receives data from a Repository – HeroRepository that pulls data from the underlying database it is connected to.
When the HeroController requests for an instance of HeroService, Spring Boot needs to first inject dependencies of the HeroService, which is a HeroRepository and from there it needs to create instance of the HeroRepository followed by an instance of the HeroService which needs an instance of the HeroRepository and finally an instance of the HeroController that needs an instance of the HeroService.
Spring Boot automatically tries to connect all the underlying sub-dependencies of a dependency while resolving the dependency itself for a requesting class.
This process of auto scanning and wiring is called – auto wiring, which is one of the interesting and powerful process of Dependency Injection in Spring Boot.
In this article, let us see how Autowiring in Spring Boot works with an illustrating example and the various scenarios that can occur while developing an Application with Spring Boot.
What is Autowiring?
Autowiring is the concept of resolving dependencies of a dependency. For example, say you have an application that has a hierarchy like this controller -> service -> repository, where the controller has a dependency on the service which internally has a dependency on the repository.
All these are marked for dependency injection, then Spring internally resolves all the dependencies of service and repository Beans before injecting the service bean into the controller bean.
This internal connection of the necessary dependencies of the beans is called Autowiring. When we use the @Autowired annotation, this also indicates that we are specifying which are dependencies that need to be wired while injecting as dependency.
Demonstrating Autowiring with an Example
To demonstrate this case, let’s look at our HeroController -> HeroService -> HeroRepository dependency resolution in our application.
The HeroController code looks like below –
package com.myjpa.helloapp.controllers;
import com.myjpa.helloapp.services.HeroService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/hero")
public class HeroController {
public HeroController() {
System.out.println("HeroController object creating");
}
@Autowired
private HeroService heroService;
@GetMapping("/")
public String getHero() {
return "I'm a Super Hero!";
}
@GetMapping("/getHeroes")
public List<String> getHeroes() {
return heroService.getHeroes();
}
}
Observe that I have injected HeroService via Autowiring. It’s not really necessary to only do Autowiring as we saw before – I’m just doing it here for demonstration purpose only.
Now if we inspect the code for HeroService, we can see another dependency internally which is HeroRepository –
package com.myjpa.helloapp.services;
import com.myjpa.helloapp.repositories.HeroRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class HeroService {
public HeroService() {
System.out.println("HeroService object creating");
}
@Autowired
private HeroRepository heroRepository;
public List<String> getHeroes() {
return heroRepository.getHeroes();
}
}
Now HeroRepository is another Spring Bean that is to be injected within HeroService for HeroService itself to be instantiated.
package com.myjpa.helloapp.repositories;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class HeroRepository {
public HeroRepository() {
System.out.println("HeroRepository object creating");
}
public List<String> getHeroes() {
return List.of("Batman", "Superman", "Spiderman", "Aquaman", "Phantom", "Heman", "Hooman");
}
}
Now when we run the application, we can see that Spring Boot goes through each of the class in the order of their invocation and then resolves its dependencies one by one –
What is a Bean Id?
By default, Spring Boot creates an identifier for each class that is registered as a Spring Bean which it calls the bean id. This bean id is just the class name in camel case notation. So the bean id for HeroService is heroService, HeroRepository is heroRepository and so on.
Now let’s say for example, there are multiple beans registered that have same type – now how will Spring Boot resolve this?
Resolving Spring Bean Ambiguity with Qualifier and Primary
When we register and inject dependencies, or when Spring tries to Autowire dependencies there can be situations where for the same Type there could be multiple Beans that qualify.
For example there could be multiple JdbcTemplates that are created for the different database connections the application makes, or different restTemplates that the application uses for different endpoints.
In such cases the application may throw an error saying that multiple Beans qualify for the dependency. In such cases we need to explicitly specify which bean we are referring to. For this purpose we have two approaches –
@Qualifier
@Qualifier
annotation specifies which bean to be injected in case there are multiple Beans that qualify for the Injection (as in the above examples). This annotation takes a single string parameter where we pass the bean id of the Bean.
A bean id is the class name of the Bean written in camel case. This is useful when we already know which Bean is needed for that particular scenario.
@Primary
@Primary
annotation is placed at the Bean, to indicate that this particular Bean needs to be considered if there are multiple Beans that qualify for the Type. In this case the Bean is picked up during the runtime when the application tries to inject the dependency and there is no need to specifically mention any bean id.
Demonstrating Bean Ambiguity with an Example
To demonstrate this, lets say we have another implementation of HeroRepository called CloudHeroRepository, which let’s say connects to a different data source to pull data and return.
Now, to have the flexibility to swap the Repositories as a pattern, let’s say we have an interface IHeroRepository that declares a single method getHeroes().
Both HeroRepository and CloudHeroRepository implement IHeroRepository and have their own definition of getHeroes() method. The approach is as below –
HeroRepository Implementation
package com.myjpa.helloapp.repositories;
import java.util.List;
public interface IHeroRepository {
List<String> getHeroes();
}
package com.myjpa.helloapp.repositories;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class HeroRepository implements IHeroRepository {
public HeroRepository() {
System.out.println("HeroRepository object creating");
}
public List<String> getHeroes() {
return List.of("Batman", "Superman", "Spiderman", "Aquaman", "Phantom", "Heman", "Hooman");
}
}
CloudHeroRepository Implementation
package com.myjpa.helloapp.repositories;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class CloudHeroRepository implements IHeroRepository {
public CloudHeroRepository() {
System.out.println("HeroRepository object creating");
}
public List<String> getHeroes() {
return List.of("Batman", "Superman", "Spiderman", "Aquaman", "Phantom", "Heman", "Hooman");
}
}
Now we modify the HeroService class to inject an instance of IHeroRepository instead of the concrete HeroRepository class. The definition is modified as below –
package com.myjpa.helloapp.services;
import com.myjpa.helloapp.repositories.IHeroRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class HeroService {
public HeroService() {
System.out.println("HeroService object creating");
}
@Autowired
private IHeroRepository repository;
public List<String> getHeroes() {
return repository.getHeroes();
}
}
Now observe that the variable name is also changed to match the generic IHeroRepository. This is because if we still keep the variable name as heroRepository, the concrete definition of HeroRepository is injected by default because heroRepository is the bean id of HeroRepository!
When we run the application with this setup, we get the following error –
***************************
APPLICATION FAILED TO START
***************************
Description:
Field repository in com.myjpa.helloapp.services.HeroService required a single bean, but 2 were found:
- cloudHeroRepository: defined in file [D:\MyProjects\JavaProjects\hero-rest-api\target\classes\com\myjpa\helloapp\repositories\CloudHeroRepository.class]
- heroRepository: defined in file [D:\MyProjects\JavaProjects\hero-rest-api\target\classes\com\myjpa\helloapp\repositories\HeroRepository.class]
This may be due to missing parameter name information
Action:
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
Ensure that your compiler is configured to use the '-parameters' flag.
You may need to update both your build tool settings as well as your IDE.
(See https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#parameter-name-retention)
Process finished with exit code 0
Resolving the Ambiguity with Qualifier or Primary attributes
Because both the implementations of IHeroRepository satisfy the dependency requirement, Spring Boot can’t tell which one to pick. We can resolve this by using either the Qualifier or the Primary attributes. For Qualifier, we can use the Qualifier attribute with a string parameter.
package com.myjpa.helloapp.services;
import com.myjpa.helloapp.repositories.IHeroRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class HeroService {
public HeroService() {
System.out.println("HeroService object creating");
}
@Autowired()
@Qualifier("heroRepository")
private IHeroRepository repository;
public List<String> getHeroes() {
return repository.getHeroes();
}
}
Alternatively, we can annotate our HeroRepository with a Primary attribute for it to be picked up as the primary during injection.
package com.myjpa.helloapp.repositories;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@Primary
public class HeroRepository implements IHeroRepository {
public HeroRepository() {
System.out.println("HeroRepository object creating");
}
public List<String> getHeroes() {
return List.of("Batman", "Superman", "Spiderman", "Aquaman", "Phantom", "Heman", "Hooman");
}
}
Either of this approach can help resolve the ambiguity for the Dependency resolution and inject the specified dependencies into the classes.
Conclusion
In this article, we have looked into the basic concept of Autowiring and how it works in a Spring Boot application. Autowiring helps us automatically resolve sub-dependencies of a dependency when injecting into the application without us having to do anything. One scenario where this can have some trouble is when there are multiple dependencies that are resolved for a type. This is a common scenario when we have multiple implementations of a base type and we want to inject the base type and let it be resolved during runtime.
To solve this we have two options – either explicitly qualify using Qualifier attribute or set one of the implementations as a Primary. Qualifier delays the resolution till runtime and the dependency is resolved when the application. Use this when you don’t know what will be the dependency that needs to be put until runtime.
The second approach Primary is when you already what will be thing to be used, decorate the class with Primary and that will be taken up when the application starts.