CSC/ECE 517 Fall 2010/ch5 5e dr
Dependency Injection
Overview
Every enterprise application involves handful number of objects that work together to achieve some purpose. These objects are coupled with each other and work in collaboration, making the classes highly dependent. However, creating such code involving highly coupled classes defeats an important tenet of Object oriented design- ‘loose coupling’. Loose coupling means that objects should have the minimum number of dependencies, which makes the code easy to understand, modify and debug. It also allows for greater code reusability and improves the maintainability. http://www.codeproject.com/KB/architecture/DependencyInjection.aspx This drive to decouple the code from its highly dependent objects leads to the concept of Dependency Injection.
Definition
The basic concept of Dependency Injection (DI) is that you do not create the objects themselves, but describe how they should be created. It is one of the more popular design paradigms known today, by which the objects define their dependencies, i.e. the objects or classes they work with. DI pattern provides better a software design that makes the code cleaner, since the object doesn’t need to look for other components it works with, or know their location and class. http://www.springframework.net/doc-latest/reference/html/objects.html#objects-factory-collaborators It facilitates the design of applications, using a framework that links the components, instead of the components linking themselves.
In other words, Dependency Injection, also known as Inversion Of Control (IoC), transfers the responsibility to locate and create the objects to the framework or the container which supports plug-n-play implementation, by injecting the dependencies into your code.
Features of Dependency Injection Main features of Dependency Injection Framework are:
- Better Design Pattern - Supports good Object-Oriented Design.
- Clean Code- Eliminates the need of looking up code from the application.
- Reuse - Promotes better code reusability.
- Loose Coupling- Objects are loosely coupled using DI
- Simple- It is very simple to implement, just need to inject the dependencies of any object.
- Maintainable- It is very easy to reconfigure and maintain in case of any future changes.
Dependency Injection Frameworks
Dependency Injection requires a framework that helps the developer to declare the dependencies, to locate, instantiate, and initialize the components or the objects. A developer has many available frameworks to him:
- Spring Framework – A light weight container, most widely used for DI and very large framework, which includes a lot many other features like Aspect Oriented, framework, container, etc. along with Dependency Injection.
- XWork - It is a standalone framework and mostly works in conjunction with Webworks. It is highly effective command pattern framework that provides a lot of features for DI.
- Apache Avalon- This is also a container framework which supports the dependency injection design pattern. This frameworks is now divided into sub-frameworks.
- PicoContainer - It is highly focused DI framework available.
- Hivelogic – Another DI framework to inject dependencies.
Example of Dependency Injection Framework
Let us take a simple example to understand the basic idea of DI. Suppose we have an Employee class which depends on the Department object, and we need some Assembler (DI Framework) to instantiate the Department field of Employee class, rather than Employee creates this object for itself. It will delegate this process of instantiation to any of the framework selected to do this job by using any form of configuration files, which are mostly .xml files. We will see comprehensive example using the framework in the next section.
Employee.java package myPackage; public class Employee { //dependent object ‘dept' private Department dept; public void Employee( ) { } setDepartment(dept) { this.dept=dept; } }
Department.java package myPackage; public class Department { private String name; public String getName() { return name; } public void setName(String name) { this.name=name; } }
Now, it is the framework (any one of the selected frameworks mentioned above) which will instantiate the Department object by looking into the configuration file for any existing dependencies of Employee object, and thus forms Employee object with Department instance. For example, in Spring Framework, the configuration file will be ApplicationContext.xml, where this binding of beans is done as:
ApplicationContext.xml <beans> <bean id="employee" class=" myPackage .Employee.java"> <property name="name " ref="yetAnotherBean"/> </bean> <bean id="department" class=" myPackage .Department.java"> <property name="name" value="Java"/> </bean> </beans>
thereby creating the dependency diagram as:
Here we can see that, the classes themselves do not create their own instances, but it is the framework that does this job for them, making the code cleaner, reusable and loosely coupled. In future if one wants to add on, delete or modify any of the dependent objects, we only need to update our configuration file. There is no need to look up the code all over again to find the class’s dependent objects. One can make modifications to the lines of code creating instances, which may be,
// used everywhere in code without Dependency Injection. Department dept=new Department ();
Thus it makes our code configurable and maintainable. Let us now move on and see some more examples to understand the different types of injections available within different frameworks.
Types of Dependency Injection
There are a number of Dependency Injection variants that exists to allow the developer to implement and write the loosely coupled code. The dependencies can be injected in various forms like:
- Type 1- Interface Injection
- Type 2- Setter Injection
- Type 3- Constructor Injection
Type 1- Interface Injection
Interface injection is the technique to add the dependency for interfaces in the components at run time, and components implement these interfaces based on the injected dependency. Spring framework does not support interface injection, so let us see this injection by using Avalon framework:
For example, we have an ApplicationDAO interface, while AccountApplicationDAO and CustomerApplicationDAO are the classes implementing ApplicationDAO,
ApplicationDAO.java package myPackage; public interface ApplicationDAO { void getEntity(); }
AccountApplicationDAO.java package myPackage; public class AccountApplicationDAO implements ApplicationDAO { void getEntity() { //get all accounts. } }
CustomerApplicationDAO.java package myPackage; public class CustomerApplicationDAO implements ApplicationDAO { void getEntity() { //get all customers. } }
We also have ApplicationService interface, which depends on the ApplicationDAO interface, and we have ApplicationServiceImpl class(component) implementing the ApplicationService inteface, which will do the specific implementation of the ApplicationDAO interface provided by the container as per the configuration.
ApplicationService.java package myPackage; public interface ApplicationService { getEntity(ApplicationDAO applicationDAO); }
ApplicationServiceImpl.java package myPackage; public class ApplicationServiceImpl implements ApplicationService { ApplicationDAO applicationDAO; validateEntity (ApplicationDAO applicationDAO) { this. applicationDAO= applicationDAO; applicationDAO.getEntity(); //code to validate the Entity (Account/Customer),as per the configuration in xml file. } }
Now, our main program will get the interface dependency from the container which use configuration file for lookup,
package myPackage; public class MyClass { public static void main(string[] args) { // Get the correct dependency based on configuration file ApplicationDAO dependencyDAO = getDependecny(); // Create our applicationServiceImpl class and inject the dependency ApplicationServiceImpl applicationServiceImpl = new ApplicationServiceImpl(); // call validateEntity() ((ApplicationService)applicationServiceImpl).validateEntity(dependencyDAO); } public static ApplicationDAO getDependency() { string implementClass = System.Configuration.ConfigurationManager.AppSettings["selectedClass"]; Type type = System.Type.GetType(implementClass); ApplicationDAO applicationDAO = (ApplicationDAO)Activator.CreateInstance(type); return applicationDAO; } }
Configuration File that will be used by MyClass file to lookup the dependency configurations.
App.Config <?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key=" selectedClass " value="myPackage. AccountApplicationDAO " /> </appSettings> </configuration>
Here, in this example, validateEntity() in ApplicationServiceImpl will call getEntity() using ApplicationDAO reference, and which getEntity() is invoked (of Accoun ot Customer ) actually depends on the dependency that is injected via configuration file.
Type 2- Setter Injection
Dependency can be injected using the setter method of a class. If a class is dependent on another class or object instances, then the dependency should be declared as a state (variable) of the class and the class should have a setter method for it. The dependencies are set onto public attributes of the object in need. One of the primary motive for using setters, is supporting dependency injection without having to modify the constructor of a legacy class. Another use is to allow the creation of expensive resources or services as late as possible and only when needed, which is an edge over the Constructor Injection, which we will see in few moments.
Let us take an example of Setter injection by using Spring Framework. [] Before using Spring, let us study some basic features of Springs. We are not including detailed explanation and complicacies of Springs here, for better understanding you can visit the site: [ ]
- Spring is a lightweight container which along with supporting features like Dependency Injection, also include other ones as: Container, Framework, AOP[ ], MVC, etc.
- Spring Framework supports both Setter Injection and Constructor Injection, but not Interface Injection. Spring jars that can be included are :
- spring-beans.jar
- spring-core.jar
- Packages that provide Spring Frameworks’s IoC container are:
- org.springframeworks.beans
- org.springframeworks.context
We will see Setter and Constructor Injection using Spring Framework.
The example below includes a class, DoctorService. The DoctorService class needs a data-access object for communicating with the database. Assume, the PrescriptionDAO ("order data-access object") is a class on which DoctorService depends.
DoctorService.java
package myPackage; public class DoctorService { private PrescriptionDAO prescriptionDAO; //empty constructor public DoctorService () {} public PrescriptionDAO getPrescriptionDAO() { return this.prescriptionDAO; } //setter for prescriptionDAO public setPrescriptionDAO(prescriptionDAO) { this.prescriptionDAO = prescriptionDAO; } //business logic that actually uses prescriptionDAOto access its variables and methods. public void print() {
System.out.println(“prescription: “+prescriptionDAO.prescription); System.out.println(“dose: “+prescriptionDAO.dose);
} }
PrescriptionDAO.java
package myPackage; public class PrescriptionDAO {
private String prescription; private int dose;
public PrescriptionDAO(){} public String getPrescription () {
return this.prescription; }
public void setPrescription (String prescription) {
this. prescription = prescription; }
}
In the above example, the invoking object is responsible for setting the PrescriptionDAO dependency. Now there is no code inside the class that is creating new instance of PrescriptionDAO, because this will be done by the Spring container by looking for dependencies of DoctorService inside applicationContext.xml file, which is usually stired in WEB-INF folder of web application
Spring framework uses setter method to create this dependent object, and note the <property> tag which is used inside ApplicationContext.xml file for setter injection. For Constructor injection, it uses another tag, <constructor-arg>, we will refer to it later.
ApplicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="doctorServiceId " class="myPackage.DoctorService">
<property name=" prescriptionDAO "><ref bean=" prescriptionDAOId "/></property>
</bean>
<bean id=" prescriptionDAOId" class=" myPackage.PrescriptionDAO ">
<property name="prescription" value="Take medicine regularly."/>
<property name="dose" value="3"/> </bean> </beans>
Now, Client is a class that is used to instantiate the Spring Container and retrieve the bean definitions defined inside.
MyClient.java
package myPackage; import java.io; import org.springframework.beans.factory.*; import org.springframework.beans.factory.xml.*; import org.springframework.core.io;
public class MyClient{
public static void main(String args[]){
Resource res=new ClassPathResource(“./myPackage/ ApplicationContext.xml”); BeanFactory factory =new XmlBeanFactory(res); //” doctorServiceId” is the same id defined in ApplicationContext.xml DoctorService doctorService= (DoctorService) factory.getBean(“doctorServiceId”); } }
Here, first path for the ApplicationContext is given and this file is used a s resource by the container. BeanFactory(or Container) uses this xml file to get the bean we defined inside the configuration file.
However Setter Injection adds the abstraction for the developer to know which dependencies are needed when. But it can save on modifying a lot of legacy code when introducing new methods, and can provide a performance boost if the dependency is expensive or not easily accessible.