This article presents a high level overview of Dependency Injection (DI). It aims to present the overall concept of Dependency Injection to the junior developer within the context of how it could be used in a variety of DI containers.
Dependency Injection (DI) or Inversion of Control (IOC) ?A lot of current literature often refers to the same design pattern as either Dependency Injection or Inversion of Control. I prefer Dependency Injection from my belief that it is a more specific name. I can imagine many scenarios where one would find evidence of Inversion of Control, though the resultant pattern is unlikely to be focusing on resolving dependencies (e.g. moving from a console application to a Windows Event Loop) . However should you prefer IOC, you can pretty much read the remainder of the article by replacing DI with IOC. Within IOC there are various types (creatively called as type 1, type 2, type 3). The differences within these types are not particularly relevant to the scope of this article and thus not entered into.
Also note that I have used the term Service extensively through this article. This should not be confused with Service Oriented Architectures. It simply became a lot easier to clarify the roles by identifying “Clients” and “Services” as opposed to “Client Component” and “Server Component or Supplier Component”
You work in an organization where you and your colleagues tend to travel a lot. Generally you travel by air and every time you need to catch a flight, you arrange for a pickup by a cab. You are aware of the airline agency who does the flight bookings, and the cab agency which arranges for the cab to drop you off at the airport. You know the phone numbers of the agencies, you are aware of the typical conversational activities to conduct the necessary bookings.
Thus your typical travel planning routine might look like the following :
Now if your company suddenly changed the preferred agencies and their contact mechanisms, you would be subject to the following relearning scenarios
Its not just you, but probably many of your colleagues would need to adjust themselves to the new scenario. This could lead to a substantial amount of time getting spent in the readjustment process.
Scenario 2Now lets say the protocol is a little bit different. You have an administration department. Whenever you needed to travel an administration department interactive telephony system simply calls you up (which in turn is hooked up to the agencies). Over the phone you simply state the destination, desired arrival date and time by responding to a programmed set of questions. The flight reservations are made for you, the cab gets scheduled for the appropriate time, and the tickets get delivered to you.
Now if the preferred agencies were changed, the administration department would become aware of a change, would perhaps readjust its workflow to be able to communicate with the agencies. The interactive telephony system could be reprogrammed to communicate with the agencies over the internet. However you and your colleagues would have no relearning required. You still continue to follow exactly the same protocol as earlier (since the administration department did all the necessary adaptation in a manner that you do not need to do anything differently).
Dependency Injection ?In both the scenarios, you are the client and you are dependent upon the services provided by the agencies. However Scenario 2 has a few differences.
Thats dependency injection in “real life”. This may not seem like a lot since you imagine a cost to yourself as a single person – but if you imagine a large organization the savings are likely to be substantial.
Dependency Injection in a Software ContextSoftware components (Clients), are often a part of a set of collaborating components which depend upon other components (Services) to successfully complete their intended purpose. In many scenarios, they need to know “which” components to communicate with, “where” to locate them, and “how” to communicate with them. When the way such services can be accessed is changed, such changes can potentially require the source of lot of clients to be changed.
One way of structuring the code is to let the clients embed the logic of locating and/or instantiating the services as a part of their usual logic. Another way to structure the code is to have the clients declare their dependency on services, and have some "external" piece of code assume the responsibility of locating and/or instantiating the services and simply supplying the relevant service references to the clients when needed. In the latter method, client code typically is not required to be changed when the way to locate an external dependency changes. This type of implementation is considered to be an implementation of Dependency Injection and the "external" piece of code referred to earlier is likely to be either hand coded or implemented using one of a variety of DI frameworks.
Those attempting to map the above scenario to design idioms will notice that the analogy could be applied to three different design idioms – Dependency Injection, Factory and Program to an Interface and not an Implementation. As in this analogy these three idioms are often but not necessarily used together.
The conventional approach till a few years ago was to separate the interface from the implementation. The factory pattern even allowed for hiding the complexity of instantiation. However the mechanism to “locate” the services was often left to the clients. Moreover some parts of the software also needed to be aware of the dependencies between the various services themselves and thus implicitly had to work out the appropriate sequencing of such component initialization, and had to track and manage their life cycles.
As an example, while the J2EE specification uses JNDI as a mechanism to standardize the mechanism of locating object references, such implementations often require a lot of code change when say the clients need to work in much simpler environments where say JNDI is not available.
Usage of Dependency Injection requires that the software we write to declare the dependencies, and lets the framework or the container work out the complexities of service instantiation, initialization, sequencing and supplies the service references to the clients as required.
There are a number of frameworks available today to help the developer. The ones I have found useful are :
In the following sections we shall take a scenario and implement the same first without using DI and then using the above frameworks respectively. This will allow us to get a flavor of how these frameworks can be leveraged. Note that I have tried to keep the code to the minimum with a strict focus on DI related functionalities.
The attached source pretty much implements the scenario described earlier. The general flow (starting from the main method in each “*Harness” classes) is as follows :
Some of the dependencies to be noted are :
The UML diagram for the source is shown below :
The salient differences to be observed across different frameworks are
There is no “typical” no container implementation. Some implementations would do a lookup from a service (e.g. JNDI) or interface with a set of factories (which perhaps may be implemented as a singleton). In this case I have chosen to read the class names from a property file and in turn instantiate the objects for the services using reflection.
The property file is as follows (simply contains the class names)
airline-agency-class = com.dnene.ditutorial.common.impl.SimpleAirlineAgency cab-agency-class = com.dnene.ditutorial.common.impl.ConstructorBasedCabAgency
The code to instantiate the agencies and trip planner is as follows :
AirlineAgency airlineAgency = null ;
CabAgency cabAgency = null ;
TripPlanner tripPlanner = null ;
// Get class names from property file
InputStream propertyFile = getClass().getClassLoader().
getResourceAsStream( " nocontainer-agency.properties " );
Properties properties = new Properties();
properties.load(propertyFile);
Class airlineAgencyClass = Class.forName
(properties.getProperty( " airline-agency-class " ));
Class cabAgencyClass = Class.forName
(properties.getProperty( " cab-agency-class " ));
Class tripPlannerClass = Class.forName
(properties.getProperty( " trip-planner-class " ));
if (AirlineAgency. class .isAssignableFrom(airlineAgencyClass))
{
// Instantiate airline agency
airlineAgency = (AirlineAgency) airlineAgencyClass.newInstance();
}
if (CabAgency. class .isAssignableFrom(cabAgencyClass))
{
// Instantiate CabAgency, and satisfy its dependency on an airlineagency.
Constructor constructor = cabAgencyClass.getConstructor
(new Class[]{AirlineAgency.class});
cabAgency = (CabAgency) constructor.newInstance
(new Object[]{airlineAgency});
}
if (TripPlanner. class .isAssignableFrom(tripPlannerClass))
{
Constructor constructor = tripPlannerClass.getConstructor
(new Class[]{AirlineAgency.class,CabAgency.class});
tripPlanner = (TripPlanner) constructor.newInstance
(new Object[]{airlineAgency,cabAgency});
}
return tripPlanner;
Note that the reference to the AirlineAgency is passed to the constructor of the CabAgency (to reflect the dependencies between the services).
The code to actually conduct the trip planning is in the AbstractHarness and AbstractTripPlanner and is reused across all harnesses.
AbstractHarness.java
public void runHarness() throws Exception
{
this.configure();
this.getPlanner().planTrip(
"1, Acme Street",
"11111",
"22222",
new Date(System.currentTimeMillis() + 7200000));
}
AbstractTripPlanner.java
public void planTrip(String departingAddress, String departingZipCode,
String arrivalZipCode, Date preferredArrivalTime)
{
FlightSchedule schedule = airlineAgency.bookTicket
(departingZipCode,arrivalZipCode,preferredArrivalTime);
cabAgency.requestCab(departingAddress ,schedule.getFlightNumber());
}
This requires a simple instantiation of the DefaultPicoContainer class.
Service Registrationpico = new DefaultPicoContainer();
The implementation classes are registered with PicoContainer
Dependency Declarationpico.registerComponentImplementation(SimpleAirlineAgency. class );
pico.registerComponentImplementation(ConstructorBasedCabAgency. class );
pico.registerComponentImplementation(ConstructorBasedTripPlanner. class );
Dependencies are declared by using a constructor containing the list of the (interface) classes the service is dependent upon. In the following case the ConstructorBasedCabAgency service is dependent upon the AirlineAgency interface.
public ConstructorBasedCabAgency(AirlineAgency airlineAgency)
{
this.airlineAgency = airlineAgency;
}
Similarly the ConstructorBasedTripPlanner implicitly declares its dependencies on the AirlineAgency and the CabAgency through its constructor.
Dependency Resolution
Pico resolves dependencies by looking at the constructors. The service classes are registered with PicoContainer by registering the implementation class names. The client however requests for the services using the interface class names. PicoContainer is smart enough to traverse the interface implementation hierarchy and return the appropriate implementation as requested. (Note that the no container implementation discussed earlier also instantiated the implementation using the class names).
Since the TripPlanner is dependent upon AirlineAgency and CabAgency and the AirlineAgency and CabAgency is also dependent upon the AirlineAgency, PicoContainer will automatically instantiate the AirlineAgency first, pass its reference while constructing the CabAgency, and pass both the agency references while constructing the TripPlanner. This entire sequence gets executed upon the following request being made.
pico.getComponentInstanceOfType(TripPlanner. class );
In this example, both the interface and implementation classes are declared in the xml file along. Note that the dependency between the services is not stated in the xml file, but follows later in the code. A “service-point” element in the xml file documents the data necessary for each service.
Container Initializationxml version="1.0" ?>
< module id ="com.dnene.ditutorial.hivemind" version ="1.0.0" >
< service-point id ="AirlineAgency" interface ="com.dnene.ditutorial.common.AirlineAgency" >
< create-instance class ="com.dnene.ditutorial.common.impl.SimpleAirlineAgency" />
service-point>
<service-point id="CabAgency" interface="com.dnene.ditutorial.common.CabAgency">
<invoke-factory>
<construct class="com.dnene.ditutorial.common.impl.SetterBasedCabAgency"/>
invoke-factory>
service-point>
<service-point id="TripPlanner" interface="com.dnene.ditutorial.common.TripPlanner">
<invoke-factory>
<construct class="com.dnene.ditutorial.common.impl.SetterBasedTripPlanner"/>
invoke-factory>
service-point>
module>
This will automatically configure the registry based on the xml specified earlier.
Dependency DeclarationRegistry registry = RegistryBuilder.constructDefaultRegistry();
The fact that the CabAgency requires the AirlineAgency is implicitly made clear to HiveMind by declaring the associated setter method.
Dependency Resolutionpublic void setAirlineAgency(AirlineAgency airlineAgency)
{
this.airlineAgency = airlineAgency;
}
Again the dependency resolution is automatically performed when the reference to the trip planner is requested as shown in the following snippet. Hivemind is able to traverse the dependencies and instantiates AirlineAgency, CabAgency and TripPlanner in that order and invokes setAirlineAgency() on the cab agency, and setAirlineAgency() and setCabAgency() on the TripPlanner to resolve the dependencies across them.
registry.getService(TripPlanner. class );
The implementations that need to be instantiated are declared in the XML. Dependencies between services are also declared as “property” element. Spring framework will use this information to invoke the corresponding “setter” method.
Container Initialization< beans >
< bean id ="AirlineAgency" class ="com.dnene.ditutorial.common.impl.SimpleAirlineAgency" singleton ="true" />
< bean id ="CabAgency" class ="com.dnene.ditutorial.common.impl.SetterBasedCabAgency" singleton ="true" >
< property name ="airlineAgency" >
< ref bean ="AirlineAgency" />
property>
bean>
<bean id="TripPlanner" class="com.dnene.ditutorial.common.impl.SetterBasedTripPlanner" singleton="true">
<property name="airlineAgency">
<ref bean="AirlineAgency"/>
property>
<property name="cabAgency">
<ref bean="CabAgency"/>
property>
bean>
beans>
The container initialization typically requires a reference to the xml file and an instantiation of the BeanFactory.
Dependency ResolutionClassPathResource res = new ClassPathResource( " spring-beans.xml " );
BeanFactory factory = new XmlBeanFactory(res);
References to the services are retrieved based on the 'id' specified in the xml (not the interface class). Again all the services are implicitly instantiated in the appropriate order and the setters called to resolve their dependencies.
factory.getBean( " TripPlanner " );
XWork is actually a command pattern framework with some amount of DI functionality. It is in all likelihood the least sophisticated of all DI frameworks. However I have found it a useful framework especially in situations where one has already decided to use webwork, more specifically when injecting dependencies into the action classes.
XML Declaration< components >
< component >
< scope > application scope>
<class>com.dnene.ditutorial.common.impl.SimpleAirlineAgencyclass>
<enabler>com.dnene.ditutorial.xwork.AirlineAgencyAwareenabler>
component>
<component>
<scope>applicationscope>
<class>com.dnene.ditutorial.common.impl.SetterBasedCabAgencyclass>
<enabler>com.dnene.ditutorial.xwork.CabAgencyAwareenabler>
component>
<component>
<scope>applicationscope>
<class>com.dnene.ditutorial.common.impl.SetterBasedTripPlannerclass>
<enabler>com.dnene.ditutorial.xwork.TripPlannerAwareenabler>
component>
components>
Note that XWork depends upon a notion called “enabler interface”. This is an interface which basically defines a setter method for the service. eg.
public interface CabAgencyAware
{
public void setCabAgency(CabAgency cabAgency);
}
Unlike other containers, it requires us to define interfaces AirlineAgencyAware and CabAgencyAware and TripPlannerAware and implement them appropriately. To that extent this requires tends to be a bit more intrusive than other containers.
Container InitializationThis tends to be a little bit more complex
Dependency DeclarationComponentConfiguration config = new ComponentConfiguration();
InputStream in =
XworkHarness. class .getClassLoader().getResourceAsStream( " components.xml " );
config.loadFromXml( in );
cm = new DefaultComponentManager();
config.configure(cm, " application " );
Classes declare their dependencies by implementing the necessary “enabler interfaces”. Thus each service declares its dependencies on other services by implementing the corresponding enabler interfaces. XWork uses the enabler interfaces to resolve the dependencies.
Dependency Resolutionpublic class SetterBasedCabAgency extends AbstractCabAgency implements AirlineAgencyAware { }
No surprises here. Usually a simple lookup from the component manager and all dependencies are automatically resolved.
Xwork and Webworkcm.getComponent(TripPlannerAware. class );
XWork is often used in conjunction with Webwork. In such scenarios, all you need to do is define and implement the enabler interfaces and create the xml file and use the component interceptor. The component manager interactions are all implemented by Webwork so that no coding to the ComponentManager is required so long as the dependencies are being resolved for the Action classes only. In addition it is very easy to setup different component managers for different scopes (e.g. Application, Session and Request).
As you can see, each DI framework does implement its features a little bit differently. In all fairness they could be used a little bit differently as well (eg. PicoContainer can be used with Setter based Dependency Injection, whereas Spring Framework can be used with Constructor based Dependency Injection). I have just chosen to document one of the supported mechanisms.
Its a little hard to be able to clearly declare one better than the other in a comprehensive sense. I guess you need to make up your mind what suits you the best in your context.
What should've become fairly clear with the above scenarios is that using any DI container substantially reduces the complexity of instantiating various services and resolving various dependencies between them.
Additional Features :Note that I have primarily focused on only two features viz.
DI Frameworks do offer more functionality than just that – primarily in the area of managing the configuration and lifecycle of the services. You should read up the associated documentation to find more about these features. Dependency Injection alone is likely to suffice for most applications of simple to moderate complexities. However you would perhaps want to exploit the other features as your application (and services) start getting more complex. Some of the resources listed at the end are likely to be good starting points for getting more information :
There are a few areas where there has been a fair degree of debate (often between the authors/supporters of the specific DI frameworks). Some of these are :
Constructor Based Dependencies OR Setter Based DependenciesThis refers to whether it is better to declare dependencies using constructors or by using setter methods. I am not sure if there is a clear case that can be made in favor of one or the other. I have however observed that setter based dependencies do seem simpler to manage as as the overall dependency structures start getting more complex.
XML OR Java</str