Six ways of dependency injection in Spring Boot — A Deep Dive
Tags: microservices, java, programming, spring
Published: 2023-02-12 04:21:56

# Introduction

Dependency injection in Spring can be done through

1. Constructor injection
    
2. Setters injection
    
3. Field injection
    

Now, each approach can be executed in two ways — you can:

* use Java annotations like `@Autowired` and let Spring scan for components in your codebase
    
* define each bean in your spring config XML file.
    

# Pre-requisites

Since the idea of this post is to learn about different methods of injections, our project setup is simple.

[`Laptop.java`](http://Laptop.java) class represents a Laptop. Now a Laptop has different attributes like `Display`, `HardDisk`, `Battery`. For now, let’s only consider [`HardDisk.java`](http://HardDisk.java).

```java
public class Laptop {
    private HardDisk hardDisk;

    public Laptop(HardDisk hardDisk){
        this.hardDisk = hardDisk;
    }
    public void saveData(){
        hardDisk.saveData();
    }
}
```

[`HardDisk.java`](http://HardDisk.java) is an interface which is implemented by two `HardDisk` implementations: `SanDiskHD` and `HitachiHD`.

```java
public interface HardDisk {
    public void saveData();
}
```

```java
public class HitachiHD implements HardDisk {
    public void saveData(){
        System.out.println("HitachiHD: saveData");
    }
}
```

```java
public class SanDiskHD implements HardDisk {
    public void saveData(){
        System.out.println("SanDiskHD: saveData");
    }
}
```

The point of having dependency injection is to follow the loose coupling principle in our code design.

Now we will go over the ways in which different implementations of `HardDisk` (i.e. `SanDiskHD` and `HitachiHD`) can be injected into `Laptop`.

# Injection using Annotations

* In my experience, this approach is widely used for large projects. This is also the one I use the most.
    

Spring will scan your Java code for annotations and automatically register beans in the spring container.

For enabling injection through annotations, you should:

1. Enable component scanning in your project in your spring config file.
    
    ```xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="<http://www.springframework.org/schema/beans>"
           xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
           xmlns:context="<http://www.springframework.org/schema/context>"
           xsi:schemaLocation="<http://www.springframework.org/schema/beans>
        <http://www.springframework.org/schema/beans/spring-beans.xsd>
        <http://www.springframework.org/schema/context>
        <http://www.springframework.org/schema/context/spring-context.xsd>">
        
    <!--    Enable component scan-->
        <context:component-scan base-package="com.learningspring.demo.src" />
    </beans>
    ```
    
2. Mark classes with `@Component` annotation. Optionally, you can also provide a bean id.
    
    ```xml
    @Component("hardDisk")
    public class HitachiHD implements HardDisk {
        public void saveData(){
            System.out.println("HitachiHD: saveData");
        }
    }
    ```
    
3. Fetch the bean using bean id.
    
    ```java
    public static void main(String[] args) {
    		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    		HardDisk hd = ctx.getBean("hardDisk", HardDisk.class);
    		hd.saveData();
    		SpringApplication.run(DemoApplication.class, args);
    }
    ```
    
4. Run, and see the results.
    
    ```plaintext
    20:13:24.494 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'hardDisk'
    HitachiHD: saveData
    ```
    

> 💡 Now, spring also assigns a default bean id for a bean if you don’t give one. The pattern is a camel-case version of the class name.
> 
> So a bean of type `HitachiHD` would have a bean id of `hitachiHD`.

# Injection using Annotations and Autowiring

## What is Autowiring?

Spring can automatically find beans by matching the type (class or interface) and inject it automatically.

So, if you want to inject a `HardDisk` implementation into `Laptop`, you can annotate the `HardDisk` attribute of `Laptop` class with `@Autowired`.

This will tell spring to scan all the components in your code, find a bean which matches the class of type `HardDisk`, and inject it into the `Laptop`.

For eg: If you annotate `HitachiHD` with `@Component`, it will inject `HitachiHD` into `Laptop`.

Now, there are three ways of autowiring beans:

1. Field injection
    
2. Constructor injection
    
3. Setter injection
    

## Field Injection

1. Annotate the implementation of `HardDisk` you want to inject into `Laptop` with `@Component`.
    
    ```java
    @Component
    public class HitachiHD implements HardDisk {
        public void saveData(){
            System.out.println("HitachiHD: saveData");
        }
    }
    ```
    
2. Inject the `HardDisk` bean into `Laptop` by annotating the `HardDisk` attribute with `@Autowired`.
    
    ```java
    @Component
    public class Laptop {
        @Autowired
        private HardDisk hardDisk;
    
        public void saveData(){
            hardDisk.saveData();
        }
    }
    ```
    
3. To test this autowiring, let’s get the `Laptop` bean and invoke it’s `saveData()` to see the results
    
    ```java
    public static void main(String[] args) {
    		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    		Laptop l = ctx.getBean("laptop", Laptop.class);
    		l.saveData();
    		SpringApplication.run(DemoApplication.class, args);
    	}
    ```
    

```plaintext
21:23:50.350 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'hitachiHD'
21:23:50.351 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'laptop'
HitachiHD: saveData
```

**Drawbacks:**

* Costlier than constructor-based or setter-based injection.
    
* Incentivises to have many dependencies injected into a class, which cause design issues.
    

## Constructor Injection

There are two patterns:

1. Annotating entire constructor with `@Autowired`.
    
    ```java
    @Component
    public class Laptop {
        private HardDisk hardDisk;
    
        @Autowired
        public Laptop(HardDisk hardDisk) {
            this.hardDisk = hardDisk;
        }
    
        public void saveData(){
            hardDisk.saveData();
        }
    }
    ```
    
2. Annotating only the specific attribute with with `@Autowired` in the constructor.
    
    ```java
    @Component
    public class Laptop {
        private HardDisk hardDisk;
    
        public Laptop(@Autowired HardDisk hardDisk) {
            this.hardDisk = hardDisk;
        }
    
        public void saveData(){
            hardDisk.saveData();
        }
    }
    ```
    

## Setter Injection

Here you annotate the setter method of the relevant attribute with `@Autowired`.

```java
@Component
public class Laptop {
    private HardDisk hardDisk;

    @Autowired
    public void setHardDisk(HardDisk hardDisk) {
        this.hardDisk = hardDisk;
    }

    public void saveData(){
        hardDisk.saveData();
    }
}
```

## What kind of dependency injection should you use?

A good answer might be — to follow the pattern already followed in the codebase you’re working on.

## **What if there are multiple implementations annotated with** `@Component`?

Because autowiring by type may lead to multiple candidates, it is often necessary to have more control over the selection process. One way to accomplish this is with Spring’s  `@Primary` annotation.

`@Primary` indicates that a particular bean should be given preference when multiple beans are candidates to be autowired to a single-valued dependency. If exactly one primary bean exists among the candidates, it becomes the autowired value.

If you want to always autowire `HitachiHD` implementation as a `HardDisk`, you can annotate it with `@Primary`:

```java
@Primary
@Component
public class HitachiHD implements HardDisk {
    public void saveData(){
        System.out.println("HitachiHD: saveData");
    }
}
```

# Dependency Injection with XML configuration

Another way to configure Spring runtime with constructor-based dependency injection is to use an XML configuration file.

In this case, we need to bootstrap our Spring application context using `ClassPathXmlApplicationContext`.

This approach is not used commonly, and you’ll see it rarely in professional codebases. This is because for large projects, defining each bean in XML configuration file is not practical.

## Constructor Injection

For injecting a bean into a class,

1. First, you need to accept the `HardDisk` bean in the constructor definition:
    
    ```java
    public class Laptop {
        private HardDisk hardDisk;
    
        public Laptop(HardDisk hardDisk){
            this.hardDisk = hardDisk;
        }
        public void saveData(){
            hardDisk.saveData();
        }
    }
    ```
    
2. You have to define the beans you want to inject into `Laptop` object using `constructor-arg` in `applicationContext.xml` file. `applicationContext.xml` is a spring config file from which we can the set different properties for our Spring container.
    
    ```xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="<http://www.springframework.org/schema/beans>"
           xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
           xmlns:context="<http://www.springframework.org/schema/context>"
           xsi:schemaLocation="<http://www.springframework.org/schema/beans>
        <http://www.springframework.org/schema/beans/spring-beans.xsd>
        <http://www.springframework.org/schema/context>
        <http://www.springframework.org/schema/context/spring-context.xsd>">
    
        <context:annotation-config/>
        <!--Defining beans here-->
    
        <bean id="hardDisk" class="com.learningspring.demo.src.harddisk.HitachiHD"/>
        <bean id="laptop" class="com.learningspring.demo.src.laptop.Laptop" >
            <constructor-arg ref="hardDisk"/>
        </bean>
    </beans>
    ```
    

Let’s test this thing out by fetching the Laptop bean and invoking it’s `saveData()` method.

```java
public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		Laptop l = ctx.getBean("laptop", Laptop.class);
		l.saveData();
		SpringApplication.run(DemoApplication.class, args);
	}
```

```plaintext
00:22:04.154 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'hardDisk'
00:22:04.158 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'laptop'
HitachiHD: saveData
```

So basically, we injected `HitachiHD` bean into our laptop object through its constructor.

This is called constructor injection.

## Setters Injection

1. Create a setter method in `Laptop` class for injections.
    
    ```java
    public class Laptop {
        private HardDisk hardDisk;
    
        public void setHardDisk(HardDisk hardDisk) {
            this.hardDisk = hardDisk;
        }
    
        public void saveData(){
            hardDisk.saveData();
        }
    }
    ```
    
2. Configure dependency injection in `applicationContext.xml`.
    
    ```xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="<http://www.springframework.org/schema/beans>"
           xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
           xmlns:context="<http://www.springframework.org/schema/context>"
           xsi:schemaLocation="<http://www.springframework.org/schema/beans>
        <http://www.springframework.org/schema/beans/spring-beans.xsd>
        <http://www.springframework.org/schema/context>
        <http://www.springframework.org/schema/context/spring-context.xsd>">
    
        <context:annotation-config/>
        <!--Defining beans here-->
    
        <bean id="hd" class="com.learningspring.demo.src.harddisk.HitachiHD"/>
        <bean id="laptop" class="com.learningspring.demo.src.laptop.Laptop" >
            <property name="hardDisk" ref="hd"/>
        </bean>
    </beans>
    ```
    

Let’s run our application.

```plaintext
00:37:43.878 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'hd'
00:37:43.882 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'laptop'
HitachiHD: saveData
```

So basically, we injected `HitachiHD` bean into our laptop object through its setter method.

This is called setter injection.

## Injecting Literal Values through setters

Let’s say we want to inject some hard-coded value into our object. How do we do that?

Well, we can leverage the setter methods for the literal value we want to inject and pass it in `applicationContext.xml` file.

```xml
<bean id="hd" class="com.learningspring.demo.src.harddisk.HitachiHD"/>
<bean id="laptop" class="com.learningspring.demo.src.laptop.Laptop" >
    <property name="hardDisk" ref="hd"/>
    <property name="brandName" value="Apple"/>
</bean>
```

```java
public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		Laptop l = ctx.getBean("laptop", Laptop.class);
		l.saveData();
		System.out.println(l.getBrandName());
		SpringApplication.run(DemoApplication.class, args);
	}
```

```java
00:48:23.773 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'hd'
00:48:23.777 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'laptop'
HitachiHD: saveData
Apple
```

## Injecting values from properties file

1. Create a properties file
    
2. Load properties file in the spring config file
    

```plaintext
brandName = Apple
```

1. Reference values from properties file in Java code. We can access it through `${variable_name}`.
    
    ```xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="<http://www.springframework.org/schema/beans>"
           xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
           xmlns:context="<http://www.springframework.org/schema/context>"
           xsi:schemaLocation="<http://www.springframework.org/schema/beans>
        <http://www.springframework.org/schema/beans/spring-beans.xsd>
        <http://www.springframework.org/schema/context>
        <http://www.springframework.org/schema/context/spring-context.xsd>">
    
        <context:annotation-config/>
        <context:property-placeholder location="classpath:application.properties"/>
        <!--Defining beans here-->
    
        <bean id="hd" class="com.learningspring.demo.src.harddisk.HitachiHD"/>
        <bean id="laptop" class="com.learningspring.demo.src.laptop.Laptop" >
            <property name="hardDisk" ref="hd"/>
            <property name="brandName" value="${brandName}"/>
        </bean>
    </beans>
    ```
# That's it, folks

![Coding Chronicles logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ly37n6knif8fv3bhhe8x.png)

If you like these posts, **do subscribe to the [newsletter](https://codingchronicles.adityakarad.com/newsletter)**. You'll be the first to know when a new blog post is released on Coding Chronicles.