Dependency Injection (DI) is a design pattern in which an object's dependency is injected by the framework rather than by the object itself. It reduces coupling between multiple objects as it is dynamically injected by the framework. In DI, the framework is completely responsible for reading configuration.
The advantages of DI are as follows:
In the Spring Framework, DI is used to satisfy the dependencies between objects. It exits in only two types:
The following figure gives us a better picture:
Let's consider an example where the EmployeeServiceImpl
class has an instance field employeeDao
of the EmployeeDao
type, a constructor with an argument, and a setEmployeeDao
method.
In the EmployeeServiceImpl.java
class, you'll find the following code:
public class EmployeeServiceImpl implements EmployeeService { private EmployeeDao employeeDao; public EmployeeServiceImpl(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
In the EmployeeDaoImpl.java
class, you'll find the following code:
public class EmployeeDaoImpl implements EmployeeDao { // ... }
Here, an instance of EmployeeDao
can be provided by the configuration file by either the constructor method or the setter method. Before we understand what these are in more detail, let's understand how generally two objects interact with each other to make an even more meaningful object.
When a class contains another class as instance field; for example, the EmployeeServiceImpl
class contains EmployeeDao
as its field. This is called a Has-A relationship since we say, "EmployeeServiceImpl
has an EmployeeDao
". So, without employeeDao
, EmployeeServiceImpl
cannot perform. The following code illustrates this:
public class EmployeeServiceImpl implements EmployeeService { private EmployeeDao employeeDao = null; }
So, employeeDao
is the dependency that needs to be resolved in order to make EmployeeServiceImpl
fully functional. The way to create an object of the EmployeeDao
type or, in other words, satisfy the dependency of EmployeeServiceImpl
in Java is shown here:
public class EmployeeServiceImpl implements EmployeeService { private EmployeeDao employeeDao = null; public EmployeeServiceImpl() { this.employeeDao = new EmployeeDaoImpl(); } public void setEmployeeDao() { this.employeeDao = new EmployeeDaoImpl(); } }
It is not a very good option as once the EmployeeServiceImpl
object is created, you don't have any way to have the object of the employeeDao
type swapped with a subclass implementation.
Constructor Injection is the process of injecting the dependencies of an object through its constructor argument at the time of instantiating it. In other words, we can say that dependencies are supplied as an object through the object's own constructor. The bean definition can use a constructor with zero or more arguments to initiate the bean, as shown here:
public class EmployeeServiceImpl implements EmployeeService { private EmployeeDao employeeDao = null; public EmployeeServiceImpl(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
In the preceding code, the object of the EmployeeDao employeeDao
type is injected as a constructor argument to the EmployeeServiceImpl
class. We need to configure bean definition in the configuration file that will perform Constructor Injection.
The Spring bean XML configuration tag <constructor-arg>
is used for Constructor Injection:
... <bean id="employeeService" class="org.packt.Spring.chapter2.dependencyinjection.EmployeeServiceImpl"> <constructor-arg ref="employeeDao" /> </bean> <bean id="employeeDao" class="org.packt.Spring.chapter2.dependencyinjection.EmployeeDaoImpl"> </bean> ...
In the preceding code snippet, there is a Has-A relationship between the classes, which is EmployeeServiceImpl HAS-A EmployeeDao
. Here, we inject a user-defined object as the source bean into a target bean using Constructor Injection. Once we have the employeeDao
bean to inject it into the target employeeService
bean, we need another attribute called ref
—its value is the name of the ID attribute of the source bean, which in our case is "employeeDao"
.
The <constructor-arg>
subelement of the <bean>
element is used for Constructor Injection. The <constructor-arg>
element supports four attributes. They are explained in the following table:
Attributes |
Description |
Occurrence |
---|---|---|
|
It takes the exact index in the constructor argument list. It is used to avoid ambiguity such as when two arguments are of the same type. |
Optional |
|
It takes the type of this constructor argument. |
Optional |
|
It describes the content in a simple string representation, which is converted into the argument type using the |
Optional |
|
Optional |
Here, we inject simple Java types into a target bean using Constructor Injection.
The Employee
class has employeeName
as String
, employeeAge
as int
, and married
as boolean
. The constructor initializes all these three fields.
In the Employee.java
class, you'll find the following code:
package org.packt.Spring.chapter2.constructioninjection.simplejavatype; public class Employee { private String employeeName; private int employeeAge; private boolean married; public Employee(String employeeName, int employeeAge, boolean married) { this.employeeName = employeeName; this.employeeAge = employeeAge; this.married = married; } @Override public String toString() { return "Employee Name: " + this.employeeName + " , Age:" + this.employeeAge + ", IsMarried: " + married; } }
In the beans.xml
file, you'll find the following code:
... <bean id="employee" class="org.packt.Spring.chapter2.constructioninjection.simplejavatype.Employee"> <constructor-arg value="Ravi Kant Soni" /> <constructor-arg value="28" /> <constructor-arg value="False" /> </bean> ...
In the Spring Framework, whenever we create a Spring bean definition file and provide values to the constructor, Spring decides implicitly and assigns the bean's value in the constructor by means of following key factors:
Whenever Spring tries to create the bean using Construction Injection by following the aforementioned rules, it tries to resolve the constructor to be chosen while creating Spring bean and hence results in the following situations.
If no matching constructor is found when Spring tries to create a Spring bean using the preceding rule, it throws the BeanCreationException
exception with the message: Could not resolve matching constructor
.
Let's understand this scenario in more detail by taking the Employee
class from earlier, which has three instance variables and a constructor to set the value of this instance variable.
The Employee
class has a constructor in the order of String
, int
, and boolean
to be passed while defining the bean in the definition file.
In the beans.xml
file, you'll find the following code:
... <bean id="employee" class="org.packt.Spring.chapter2.constructioninjection.simplejavatype.Employee"> <constructor-arg value="Ravi Kant Soni" /> <constructor-arg value="False" /> <constructor-arg value="28" /> </bean> ...
If the orders in which constructor-arg
is defined are not matching, then you will get the following error:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name employee defined in the classpath resource [beans.xml]: Unsatisfied dependency expressed through constructor argument with index 1 of type [int]: Could not convert constructor argument value of type [java.lang.String] to required type [int]: Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "False"
The solution to this problem is to fix the order. Either we modify the constructor-arg
order of the bean definition file or we use the index
attribute of constructor-arg
as follows:
... <bean id="employee" class="org.packt.Spring.chapter2.constructioninjection.simplejavatype.Employee"> <constructor-arg value="Ravi Kant Soni" index="0" /> <constructor-arg value="False" index="2" /> <constructor-arg value="28" index="1" /> </bean> ...
Remember that the index
attribute always starts with 0
.
Sometimes, there is no problem in resolving the constructor, but the constructor chosen is leading to inconvertible data. In this case, org.springframework.beans.factory.UnsatisfiedDependencyException
is thrown just before the data is converted to the actual type.
Let's understand this scenario in more depth; the Employee
class contains two constructor methods and both accept three arguments with different data types.
The following code snippet is also present in Employee.java
:
package org.packt.Spring.chapter2.constructioninjection.simplejavatype; public class Employee { private String employeeName; private int employeeAge; private String employeeId; Employee(String employeeName, int employeeAge, String employeeId) { this.employeeName = employeeName; this.employeeAge = employeeAge; this.employeeId = employeeId; } Employee(String employeeName, String employeeId, int employeeAge) { this.employeeName = employeeName; this.employeeId = employeeId; this.employeeAge = employeeAge; } @Override public String toString() { return "Employee Name: " + employeeName + ", Employee Age: " + employeeAge + ", Employee Id: " + employeeId; } }
In the beans.xml
file, you'll find the following code:
... <bean id="employee" class="org.packt.Spring.chapter2.constructioninjection.simplejavatype.Employee"> <constructor-arg value="Ravi Kant Soni" /> <constructor-arg value="1065" /> <constructor-arg value="28" /> </bean> ...
Spring chooses the wrong constructor to create the bean. The preceding bean definition has been written in the hope that Spring will choose the second constructor as Ravi Kant Soni
for employeeName
, 1065
for employeeId
, and 28
for employeeAge
. But the actual output will be:
Employee Name: Ravi Kant Soni, Employee Age: 1065, Employee Id: 28
The preceding result is not what we expected; the first constructor is run instead of the second constructor. In Spring, the argument type 1065
is converted to int
, so Spring converts it and takes the first constructor even though you assume it should be a string.
In addition, if Spring can't resolve which constructor to use, it will prompt the following error message:
constructor arguments specified but no matching constructor found in bean 'CustomerBean' (hint: specify index and/or type arguments for simple parameters to avoid type ambiguities)
The solution to this problem is to use the type
attribute to specify the exact data type for the constructor:
... <bean id="employee" class="org.packt.Spring.chapter2.constructioninjection.simplejavatype.Employee"> <constructor-arg value="Ravi Kant Soni" type="java.lang.String"/> <constructor-arg value="1065" type="java.lang.String"/> <constructor-arg value="28" type="int"/> </bean> ...
Now the output will be as expected:
Employee Name: Ravi Kant Soni, Employee Age: 28, Employee Id: 1065
The setter-based DI is the method of injecting the dependencies of an object using the setter method. In the setter injection, the Spring container uses setXXX()
of the Spring bean class to assign a dependent variable to the bean property from the bean configuration file. The setter method is more convenient to inject more dependencies since a large number of constructor arguments makes it awkward.
In the EmployeeServiceImpl.java
class, you'll find the following code:
public class EmployeeServiceImpl implements EmployeeService { private EmployeeDao employeeDao; public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
In the EmployeeDaoImpl.java
class, you'll find the following code:
public class EmployeeDaoImpl implements EmployeeDao { // ... }
In the preceding code snippet, the EmployeeServiceImpl
class defined the setEmployeeDao()
method as the setter method where EmployeeDao
is the property of this class. This method injects values of the employeeDao
bean from the bean configuration file before making the employeeService
bean available to the application.
The Spring bean XML configuration tag <property>
is used to configure properties. The ref
attribute of property elements is used to define the reference of another bean.
In the beans.xml
file, you'll find the following code:
... <bean id="employeeService" class="org.packt.Spring.chapter2.dependencyinjection.EmployeeServiceImpl"> <property name="employeeDao" ref="employeeDao" /> </bean> <bean id="employeeDao" class="org.packt.Spring.chapter2.dependencyinjection.EmployeeDaoImpl"> </bean> ...
The <property>
element invokes the setter method. The bean definition can be describing the zero or more properties to inject before making the bean object available to the application. The <property>
element corresponds to JavaBeans' setter methods, which are exposed by bean classes. The <property>
element supports the following three attributes:
Attributes |
Description |
Occurrence |
---|---|---|
|
It takes the name of Java bean-based property |
Optional |
|
It describes the content in a simple string representation, which is converted into the argument type using JavaBeans' |
Optional |
|
It refers to a bean |
Optional |
Here, we inject string-based values using the setter method. The Employee
class contains the employeeName
field with its setter method.
In the Employee.java
class, you'll find the following code:
package org.packt.Spring.chapter2.setterinjection; public class Employee { String employeeName; public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } @Override public String toString() { return "Employee Name: " + employeeName; } }
In the beans.xml
file, you'll find the following code:
... <bean id="employee" class="org.packt.Spring.chapter2.setterinjection.Employee"> <property name="employeeName" value="Ravi Kant Soni" /> </bean> ...
In the preceding code snippet, the bean configuration file set the property value.
In the PayrollSystem.java
class, you'll find the following code:
package org.packt.Spring.chapter2.setterinjection; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class PayrollSystem { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "beans.xml"); Employee employee = (Employee) context.getBean("employee"); System.out.println(employee); } }
The output after running the PayrollSystem
class will be as follows:
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1ba94d: startup date [Sun Jan 25 10:11:36 IST 2015]; root of context hierarchy Jan 25, 2015 10:11:36 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from class path resource [beans.xml] Employee Name: Ravi Kant Soni
In the Spring IoC container, beans can also access collections of objects. Spring allows you to inject a collection of objects in a bean using Java's collection framework. Setter Injection can be used to inject collection values in the Spring Framework. If we have a dependent object in the collection, we can inject this information using the ref
element inside the list, set, or map. Let's discuss them in more detail:
<list>
: This element describes a java.util.List
type. A list can contain multiple bean
, ref
, value
, null
, another list
, set
, and map
elements. The necessary conversion is automatically performed by BeanFactory
.<set>
: This element describes a java.util.Set
type. A set can contain multiple bean
, ref
, value
, null
, another set
, list
, and map
elements.<map>
: This element describes a java.util.Map
type. A map can contain zero or more <entry>
elements, which describes a key and value.The Employee
class is a class with an injecting collection.
In the Employee.java
class, you'll find the following code:
package org.packt.Spring.chapter2.setterinjection; import java.util.List; import java.util.Map; import java.util.Set; public class Employee { private List<Object> lists; private Set<Object> sets; private Map<Object, Object> maps; public void setLists(List<Object> lists) { this.lists = lists; } public void setSets(Set<Object> sets) { this.sets = sets; } public void setMaps(Map<Object, Object> maps) { this.maps = maps; } }
The bean configuration file is the one that injects each and every property of the Employee
class.
In the beans.xml
file, you'll find the following code:
... <bean id="employee" class="org.packt.Spring.chapter2.setterinjection.Employee"> <property name="lists"> <list> <value>Ravi Kant Soni</value> <value>Shashi Kant Soni</value> <value>Shree Kant Soni</value> </list> </property> <property name="sets"> <set> <value>Namrata Soni</value> <value>Rishi Raj Soni</value> </set> </property> <property name="maps"> <map> <entry key="Key 1" value="Sasaram"/> <entry key="Key 2" value="Bihar"/> </map> </property> </bean> ...
In the preceding code snippet, we injected values of all three setter methods of the Employee
class. The List
and Set
instances are injected with the <list>
and <set>
tags. For the map
property of the Employee
class, we injected a Map
instance using the <map>
tag. Each entry of the <map>
tag is specified with the <entry>
tag that contains a key-value pair of the Map
instance.
Similar to the concept of inner classes in Java, it is also possible to define a bean inside another bean; for example, in an Automated Teller Machine (ATM) system, we can have a printer bean as an inner bean of the ATM
class.
The following are the characteristics of inner beans in Spring:
Printer
class is not an inner class, but a printer bean is defined as an inner bean.The limitations of using inner beans are as follows:
An ATM
class has a Printer
class. We'll declare the printer bean as an inner bean (inside the enclosing ATM bean) since the Printer
class is not referenced anywhere outside the ATM
class. The printBalance()
method of ATM delegates the call to the printBalance()
method of the printer. The printer bean will be declared as an inner bean and will then be injected into the ATM bean using Setter Injection.
The ATM
class delegates the call to print the balance to the Printer
class.
The following code snippet can also be found in ATM.java
:
package org.packt.Spring.chapter2.setterinjection; public class ATM { private Printer printer; public Printer getPrinter() { return printer; } public void setPrinter(Printer printer) { this.printer = printer; } public void printBalance(String accountNumber) { getPrinter().printBalance(accountNumber); } }
In the preceding code snippet, the ATM
class has a Printer
class as property and the setter
, getPrinter()
, and printBalance()
methods.
In the Printer.java
class, you'll find the following code:
package org.packt.Spring.chapter2.setterinjection; public class Printer { private String message; public void setMessage(String message) { this.message = message; } public void printBalance(String accountNumber) { System.out.println(message + accountNumber); } }
In the preceding code snippet, the Printer
class has the printBalance()
method. It has a message
property, and a setter
method sets the message value from the bean configuration file.
In the beans.xml
file, you'll find the following code:
... <bean id="atmBean" class="org.packt.Spring.chapter2.setterinjection.ATM"> <property name="printer"> <bean class="org.packt.Spring.chapter2.setterinjection.Printer"> <property name="message" value="The balance information is printed by Printer for the account number"></property> </bean> </property> </bean> ...
Here, we declare atmBean
. We declare the printer bean as an inner bean by declaring inside the enclosing atmBean
. The id
attribute cannot be used outside the context of atmBean
and hence hasn't been provided to the printer bean.
We come across two cases while injecting null and empty string values.