Explore Amazon Deals

「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:

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:

  1. 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.
  2. If there is a constructor, the bean will be injected through the constructor parameters.
  3. 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.

Next Post Previous Post
No Comment
Add Comment
comment url