「Spring Boot #2」 @Autowired - @Primary - @Qualifier
Introduction
Before diving into this section, you may want to explore how @Autowired
operates by checking:
Guide on @Component and @Autowired
In this article, we will explore how @Autowired
works and how to use two annotations, @Primary
and @Qualifier
.
Installation:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.blogspot.amztopfind</groupId> <artifactId>spring-boot-tutorial</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging> <name>spring-boot-tutorial</name> <description>Everything about Spring Boot</description> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Directory Structure:
Injecting Spring Beans:
The method to inject Spring Beans involves using @Autowired
, which signals to Spring that it will automatically inject the corresponding bean into the marked position.
Explanation:
@Autowired
is an annotation in Spring used for automatic dependency injection.- When applied to a field, constructor, or method,
@Autowired
informs Spring to automatically inject a bean of the corresponding type at the annotated position. - This streamlines the process of connecting different components within a Spring application by handling bean injection automatically.
// Marked as a Spring component @Component public class Girl { // Annotation to indicate Spring to inject an Outfit object here @Autowired Outfit outfit; // Alternative constructor without annotations // public Girl(Outfit outfit) { // this.outfit = outfit; // } // Getter and Setter methods could be here }
After finding a class marked with @Component
, the process of injecting beans occurs as follows:
- If the class does not have a constructor or setter method, Java Reflection is used to inject the object into the property annotated with
@Autowired
. - If there is a constructor, the bean will be injected through the constructor parameters.
- If there is a setter method, the bean will be injected through the method's parameters.
In the example above, I used Java Reflection to inject the bean into the Girl
class. If you don't use @Autowired
, you would need an alternative constructor or a corresponding setter method.
// Marked as a Spring component @Component public class Girl { // Annotation to indicate Spring to inject an Outfit object here @Autowired Outfit outfit; // Spring will inject the outfit through the constructor first public Girl() { } // If a satisfying constructor is not found, Spring will use this setter method public void setOutfit(Outfit outfit) { this.outfit = outfit; } // Getter and Setter methods could be here }
You can also annotate a method with @Autowired
instead of a property. The functionality remains the same; it will search for a matching bean for that method and inject it.
// Marked as a Spring component @Component public class Girl { // Annotation to indicate Spring to inject an Outfit object here Outfit outfit; // Spring will inject the outfit through the constructor first public Girl() { } // If a satisfying constructor is not found, Spring will use this setter method @Autowired public void setOutfit(Outfit outfit) { this.outfit = outfit; } // Getter and Setter methods could be here }
The challenge with @Autowired
:
In practice, there are situations where we use @Autowired
when Spring Boot contains two beans of the same type in the context.
In this case, Spring may become confused and unsure about which bean to use for injection into the object.
For example:
The Outfit
class has two subclasses, Bikini
and Naked
.
// Interface representing an Outfit public interface Outfit { void wear(); } /* * Marked with @Component * This class will be understood by Spring Boot as a Bean (or dependency) * and will be managed by Spring Boot */ @Component public class Bikini implements Outfit { @Override public void wear() { System.out.println("Wearing a bikini"); } } @Component public class Naked implements Outfit { @Override public void wear() { System.out.println("Not wearing anything"); } }
The Girl
class requires the injection of an Outfit
for itself.
@Component public class Girl { @Autowired Outfit outfit; // GET // SET }
At this point, when running the program, Spring Boot will report an error as follows.
Output:
*************************** APPLICATION FAILED TO START *************************** Description: Field outfit in com.blogspot.amztopfind.helloprimaryqualifier.Girl required a single bean, but 2 were found: - bikini: defined in file [E:\Bang\JavaSpringProject\spring-boot-tutorial\spring-boot-2-helloworld-Primary-Qualifier\target\classes\com\blogspot\amztopfind\helloprimaryqualifier\Bikini.class] - naked: defined in file [E:\Bang\JavaSpringProject\spring-boot-tutorial\spring-boot-2-helloworld-Primary-Qualifier\target\classes\com\blogspot\amztopfind\helloprimaryqualifier\Naked.class]
The first solution is to use the @Primary
annotation.
@Primary
is an annotation applied to a bean, ensuring it is always the preferred choice when there are multiple beans of the same type in the context.
In the example above, if we designate Naked
as the primary, the program will run smoothly.
And obviously, the Outfit
inside Girl
will be Naked
.
// Marked as a Spring component and set as primary bean @Component @Primary public class Naked implements Outfit { @Override public void wear() { System.out.println("Not wearing anything"); } }
Run the program to see the result:
@SpringBootApplication public class App { public static void main(String[] args) { // The ApplicationContext is the container that holds all the Beans. ApplicationContext context = SpringApplication.run(App.class, args); // After running, the context will contain Beans marked with @Component. // Retrieving the Girl bean from the context. Girl girl = context.getBean(Girl.class); System.out.println("Girl Instance: " + girl); System.out.println("Girl Outfit: " + girl.outfit); // Wearing the outfit. girl.outfit.wear(); } }
Output:
Girl Instance: com.blogspot.amztopfind.helloprimaryqualifier.Girl@17786713 Girl Outfit: com.blogspot.amztopfind.helloprimaryqualifier.Naked@13412087 Not wearing anything
Spring Boot prioritized Naked
and injected it into Girl
.
@Qualifier:
The second approach is to use the @Qualifier
annotation.
@Qualifier
specifies the name of a bean that you want to designate for injection.
For example:
// Represents an outfit public interface Outfit { void wear(); } // A bikini outfit implementation @Component("bikini") public class Bikini implements Outfit { @Override public void wear() { System.out.println("Wearing a bikini"); } } // An outfit representing not wearing anything @Component("naked") public class Naked implements Outfit { @Override public void wear() { System.out.println("Not wearing anything"); } } // A Girl class representing a person with an outfit @Component public class Girl { Outfit outfit; // Marked for Spring to inject an Outfit object here public Girl(@Qualifier("naked") Outfit outfit) { // Spring will inject the outfit through the first constructor // Additionally, it will look for a Bean with @Qualifier("naked") in the context for injection this.outfit = outfit; } // Getter and Setter methods could be here }
Conclusion:
@Primary
and @Qualifier
are some of the features you should be aware of in Spring to handle the scenario of having multiple beans of the same type within a project.