Introduction
Dependency injection in Spring can be done through
Constructor injection
Setters injection
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 codebasedefine 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
class represents a Laptop. Now a Laptop has different attributes like Display
, HardDisk
, Battery
. For now, let’s only consider HardDisk.java
.
public class Laptop {
private HardDisk hardDisk;
public Laptop(HardDisk hardDisk){
this.hardDisk = hardDisk;
}
public void saveData(){
hardDisk.saveData();
}
}
HardDisk.java
is an interface which is implemented by two HardDisk
implementations: SanDiskHD
and HitachiHD
.
public interface HardDisk {
public void saveData();
}
public class HitachiHD implements HardDisk {
public void saveData(){
System.out.println("HitachiHD: saveData");
}
}
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:
-
Enable component scanning in your project in your spring config file.
<?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>
-
Mark classes with
@Component
annotation. Optionally, you can also provide a bean id.
@Component("hardDisk") public class HitachiHD implements HardDisk { public void saveData(){ System.out.println("HitachiHD: saveData"); } }
-
Fetch the bean using bean id.
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); }
-
Run, and see the results.
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 ofhitachiHD
.
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:
Field injection
Constructor injection
Setter injection
Field Injection
-
Annotate the implementation of
HardDisk
you want to inject intoLaptop
with@Component
.
@Component public class HitachiHD implements HardDisk { public void saveData(){ System.out.println("HitachiHD: saveData"); } }
-
Inject the
HardDisk
bean intoLaptop
by annotating theHardDisk
attribute with@Autowired
.
@Component public class Laptop { @Autowired private HardDisk hardDisk; public void saveData(){ hardDisk.saveData(); } }
-
To test this autowiring, let’s get the
Laptop
bean and invoke it’ssaveData()
to see the results
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); }
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:
-
Annotating entire constructor with
@Autowired
.
@Component public class Laptop { private HardDisk hardDisk; @Autowired public Laptop(HardDisk hardDisk) { this.hardDisk = hardDisk; } public void saveData(){ hardDisk.saveData(); } }
-
Annotating only the specific attribute with with
@Autowired
in the constructor.
@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
.
@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
:
@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,
-
First, you need to accept the
HardDisk
bean in the constructor definition:
public class Laptop { private HardDisk hardDisk; public Laptop(HardDisk hardDisk){ this.hardDisk = hardDisk; } public void saveData(){ hardDisk.saveData(); } }
-
You have to define the beans you want to inject into
Laptop
object usingconstructor-arg
inapplicationContext.xml
file.applicationContext.xml
is a spring config file from which we can the set different properties for our Spring container.
<?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.
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);
}
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
-
Create a setter method in
Laptop
class for injections.
public class Laptop { private HardDisk hardDisk; public void setHardDisk(HardDisk hardDisk) { this.hardDisk = hardDisk; } public void saveData(){ hardDisk.saveData(); } }
-
Configure dependency injection in
applicationContext.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.
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.
<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>
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);
}
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
Create a properties file
Load properties file in the spring config file
brandName = Apple
-
Reference values from properties file in Java code. We can access it through
${variable_name}
.
<?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
If you like these posts, do subscribe to the newsletter. You'll be the first to know when a new blog post is released on Coding Chronicles.
Top comments (0)