This article aims to introduce the notion of Inversion Of Control (IoC) and how it can streamline application design. We will look at the different types of IoC frameworks. By showing how IoC can result in simpler, more flexible code, you'll also be able to see why IoC has attracted so much interest of late.
The Theory of IoC
The best way to describe what IoC is about, and what benefits it can provide, is to look at a simple example. The following
JDBCDataManger
class is used to manage our application's accessing of the database. This application is currently using raw JDBC for persistence. To access the persistence store via JDBC, the
JDBCDataManger
will need a
DataSource
object. The standard approach would be to hard code this
DataSource
object into the class, like this:
public class JDBCDataManger {
public void accessData() {
DataSource dataSource = new DataSource();
//access data
...
}
Given that
JDBCDataManger
is handling all data access for our application, hard coding the
DataSource
isn't that bad, but we may want to further abstract the
DataSource
, perhaps getting it via some system-wide property object:
public class JDBCDataManger {
public void accessData() {
DataSource dataSource =
ApplciationResources.getDataSource();
}
In either case, the
JDBCDataManger
has to fetch the
DataSource
itself.
IoC takes a different approach — with IoC, the
JDBCDataManger
would declare its need for a
DataSource
and have one provided to it by an IoC framework. This means that the component would no longer need to know how to get the dependency, resulting in cleaner, more focused, and more flexible code.
IoC Frameworks
The ideas behind IoC aren't especially new; in fact, many have remarked that IoC is nothing but a new acronym for the older Dependency Inversion Principle (PDF file). What is new is the interest in IoC, and the large number of frameworks being actively developed to aid the use of IoC.
IoC frameworks are the facilitators for the IoC pattern — think of a framework's job as being the glue for connecting the components in an IoC system. While the general principle of IoC is fairly straightforward, there are several distinct implementations evident in the frameworks. The developers of PicoContainer originally defined the three types of IoC, in order to differentiate their approach from the other frameworks around at the time. At first, these types were simply called Types 1,2, and 3, but in Martin Fowler's recent article, "Inversion of Control Containers and the Dependency Injection Pattern," he coined some more informative terms for these three types, which we will use below.
In the rest of the article, we'll look briefly at Avalon, and in more depth at the two most popular IoC frameworks, Spring and PicoContainer, and the types of IoC they provide.
Interface Injection (Type 1)
With Interface Injection IoC, components implement specific interfaces provided by their containers in order to be configured. Let's look at a refactor of our
JDBCDataManager
that uses the Avalon framework:
import org.apache.avalon.framework.*;
public class JDBCDataManger implements Serviceable {
DataSource dataSource;
public void service (ServiceManager sm)
throws ServiceException {
dataSource = (DataSource)sm.lookup("dataSource");
}
public void getData() {
//use dataSource for something
}
}
This form of IoC has been around for longer than the term IoC has been in use — many of you might have used such a form of IoC when using the EJB framework, for example. Here, your components extend and implement specified interfaces, which then get called by the framework itself.
The fact that the Avalon framework has been providing an IoC framework for several years now, without generating nearly as much interest in the idea as either Spring or PicoContainer, is probably due to the downsides of this approach. The requirement to implement specific interfaces can give code a "bloated" feel, while at the same time coupling your application code to the underlying framework. The benefits provided by the other two forms of IoC we will look at next far outweigh those provided by this form of IoC.
Setter Injection (Type 2)
With Setter Injection IoC, some external metadata is used to resolve dependencies. In Spring, this metadata takes the form of an XML configuration file. With this form of IoC, the
JDBCDataManager
class looks like a normal bean:
public class JDBCDataManger {
private DataSource dataSource;
public void setDataManager(DataSource dataSource {
this.dataSource = dataSource;
}
public void getData() {
//use dataSource for something
}
}
Our
JDBCDataManger
component exposes its
dataSource
property to allow Spring to set it. Spring does this using its XML configuration. First we define a data source bean (which can be reused by multiple components):
<bean id="myDataSource"
class="org.apache.commons.dbcp.BasicDataSource" >
<property name="driverClassName">
<value>com.mydb.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mydb://server:port/mydb</value>
</property>
<property name="username">
<value>root</value>
</property>
</bean>
Next, we define an instance of our manager and pass in a reference to the data source:
<bean id="dataManager"
class="example.JDBCDataManger">
<property name="dataSource">
<ref bean="myDataSource"/>
</property>
</bean>
At runtime, a
JDBCDataManger
class will be instantiated with the correct
DataSource
dependency resolved, and we will be able to access the bean via the Spring framework itself.
The definition of dependencies in this way makes unit testing a breeze: simply define an XML file for your mock objects, replacing your normal XML file, and away you go.
Perhaps the main advantage of Setter Injection is that application code is not tied to the container in any way, but this is also a downside — it's not immediately clear how this
JDBCDataManger
component relates to everything else. It almost seems as though the
DataSource
is being magically passed to the
JDBCDataManger
, as the dependency management is being done outside of the Java code. Another disadvantage