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):
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 is that Spring requires getters and setters for its dependency resolution. You have to expose properties that you might perhaps not otherwise expose, potentially breaking data encapsulation rules and, at best, making a class' interface more complex than is needed. With Spring's metadata being described in XML, it cannot be validated at compile time during normal Java compilation, meaning issues with broken dependencies can only be spotted at runtime.
Constructor Injection (Type 3)
Constructor Injection is based around this principle of the "Good Citizen." The Good Citizen pattern was introduced by Joshua Bloch, to describe objects that, upon creation, are fully set up and valid to use. In practice, this means that
objects should not need additional methods to be called on them after instantiation in order to make them useable.
The result is that you can sure that once you've created such an object, it's fit to use. This radically
simplifies your code and reduces the need for defensive coding checks, while at the same time making your code
more defensive as a whole. Such code is also very easy to test.
With Constructor Injection, you register an object with the framework, specify the parameters to use (which can in turn be created by the framework itself) and then just request an instance. The components being registered just have to implement a constructor, which can be used to inject the dependencies. Recently, Spring has introduced support for this form of IoC, but we'll look instead at PicoContainer, which has been built around this principle. Let's look at our
JDBCDataManger, now recoded for use with a Constructor Injection framework:
public class JDBCDataManger {
private DataSource dataSource;
public JDBCDataManger(DataSource dataSource) {
this.dataSource = dataSource;
}
public void getData() {
//use dataSource for something
}
}
Rather than using a configuration file, PicoContainer uses some good old-fashioned Java to glue everything
together:
//create a datasource bean
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mydb.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("Bob");
//create the container
MutablePicoContainer pico = new DefaultPicoContainer();
//register components with container
ConstantParameter dataSourceParam =
new ConstantParameter(dataSource);
String key = "DataManager";
Parameter[] params = {dataSourceParam};
/*
* Now each request for a DataManager will instantiate
* a JDBCDataManager object with our defined
* dataSource object
*/
pico.registerComponentImplementation (key,
JDBCDataManger.class,params);
To get instances of the JDBCDataManager object, we just have to reference the class by its key:
Like Setter Injection IoC, our application code (apart from the Pico-specific configuration) is independent of the framework itself, and also gives you the advantages inherited from the use of the Good Citizen pattern.
In addition, given that PicoContainer only requires a constructor, we have to make much less provisioning for the use
of an IoC framework than with Setter Injection IoC.
Potential downsides with this approach are that using constructors to maintain the dependencies can become
more complex when using inheritance, and it can cause issues if you use your constructors for purposes other than
simply initializing the object. (Which some do!)
IoC in Action: The Data Access Object Pattern
Currently, our JDBCDataManger class is using SQL to access our data. What if we wanted to access data
via Hibernate, or JDO? We could replace the uses of JDBCDataManger with a new class, but a more elegant solution would be to use the Data Access Object (DAO) pattern. In brief, the DAO pattern defines a method by which normal value objects are used to set and retrieve data (think normal JavaBeans), and this access is done via an abstract Data Access Object. (For those of you wishing to learn more on the DAO pattern, Sun's Pattern Catalog
is a good place to start.)
Our existing JDBCDataManger will remain unchanged. We will add an Interface called DataManager, which will implement our data access methods. For the sake of argument, we'll also add a Hibernate implementation, HibernateDataManager. Both JDBCDataManger and
HibernateDataManager become Data Access Objects.
Assuming we were already using IoC, changing which DAO to use is a breeze. Assuming we use Spring with the code above, we can use Hibernate instead of JDBC by simply changing the XML config to resolve to the HibernateDataManager rather than the JDBCDataManger class. Likewise for PicoContainer: we just register the HibernateDataManager class instead of the JDBCDataManger.
When switching between DAO implementations, our application code will remain unchanged, assuming they are just
expecting the DataManager interface rather than the JDBCDataManger implementation.
By using a DAO interface with two implementations, we are combining the DAO pattern with the
Abstract Factory pattern. In effect, the IoC frameworks are undertaking the role of the factory itself. Using such a pattern during development makes moving to another persistence mechanism very easy indeed, and can be of great use if your development environment uses a slightly different setup than your release environment. Both implementations of the DataManager can be in the same codebase, and switching between them is trivial.
Spring Vs. PicoContainer
PicoContainer and Spring differ little in the amount of work required to
set up your IoC bindings. Spring can be configured either by an XML configuration
file or directly in Java, whereas PicoContainer requires a Java binding
(although the PicoExtras project does provide XML configuration support). I am rapidly
coming to the conclusion that XML configuration files are becoming overused
(and as someone has recently noted, all of these different configuration files are
almost becoming new languages in their own right), although which approach is better is very much a matter of
personal taste. Given that you may require multiple configurations
(say, one for development, another for release), an XML-based system may
be preferable, if only for configuration management issues.
Both are fairly "light" frameworks — they work well with other frameworks and have
the added advantage of not tying you to them. PicoContainer is by far the
smaller of the two; it sticks to the job of being an IoC framework and
doesn't provide many supporting classes for working with external products
like Hibernate. It is also worth noting that Spring is not just an IoC framework: it also provides web application and AOP frameworks, as well as some general support classes, which adds
significantly to the size of the library. Personally,
I found Spring's web application framework very flexible. However, it does seem to
require more in the way of configuration than a framework such as Struts, and yet
doesn't have as rich a set of supporting classes.
In addition, the AOP features are still being developed, and you may not find it as fully
featured as "pure" AOP frameworks such as AspectWerkz or AspectJ.
If you are going to use the additional supporting classes provided by Spring, then
you'll find it a good choice. If, however, you are happy to implement these
yourself (or rely on external projects to provide them) then Pico's small footprint
might be a deciding factor. Both support Constructor Injection IoC, but only Spring supports
Setter Injection -- if you prefer setter injection to constructor injection, then Spring is the obvious choice.
Conclusion
Hopefully, I have shown that by using IoC in your application that you can end
up with neater, more flexible code. Both Spring and PicoContainer can be
easily introduced into existing projects as part of ongoing refactoring work, and
their introduction can further aid future refactoring work. Those adopting IoC from
project inception will find their application code has better defined inter-component
relationships, and will be more flexible and easier to test.
A new IoC engine: JICE
2004-11-02 00:11:43 yakuzuki
[Reply | View]
Has anyone heard of JICE?
It is a fresh XML-based configuration tool for Java which seems to support IoC as well. The XML format of JICE is more flexible than the one in Spring, for example.
JICE:
http://jicengine.sourceforge.net
IOC article by Martin Fowler
2004-02-20 04:03:31 jamesthecat
[Reply | View]
Inversion of control, or as sometimes called, dependency injection, is about, in my opinion, deferring the relations setup between objects.
When one designs the model of an application, one usually comes up with interfaces and abstract classes on which relations are defined.
At runtime this relations are represented with references from a class instance to another class instance. So the problem appears of where and when instanciate the actual implementations and setup the references.
In my opinion IoC is about this. And so IoC must be a corner stone of every component enviroment.
So, we have here a management and configuration problem. Relations setup must be done through configuration and management.
That's why one is pointed toward jmx. And not by chance jmx in jdk1.5 has a relation services.
I do not think it is about deferred setup, although this can be achived using IoC. At its heart it is about a component saying "I need this" and being given it. IoC can also be used for lifecycle control - a component says "I have a lifecycle" and the IoC framework can then manipulate it accordingly.
JMX is certainly a kind of IoC container, however it is kind of a hybrid type 1/type 2 container. Some interfaces need implementing, other operations are carried out using introspection of the classes. Some people are keeping their components (read: JMX beans) generic, and are then using a pure IoC container to wrap the component and present it as a JMX bean to the JMX server. In that way you can work with the JMX servers out there but keep your code nice and clean.
Interfaces don't need to change...
2004-02-10 10:28:44 jcarreira
[Reply | View]
You wrote:
Another disadvantage is that Spring requires getters and setters for its dependency resolution. You have to expose properties that you might perhaps not otherwise expose, potentially breaking data encapsulation rules and, at best, making a class' interface more complex than is needed.
This is not entirely accurate. Your component Interface does not need to change. It can be purely the business methods your component provides. Your implementation class, however, can have these getters and setters as purely an implementation detail.
Some comments
2004-02-10 08:00:02 mparaz
[Reply | View]
My comments here:
http://migs.paraz.com/wordpress/index.php?p=94
(What's the trackback URL for java.net articles?)
Small footprint
2004-02-10 07:37:16 colins
[Reply | View]
Sam,
W/regards to your statement <<< If you are going to use the additional supporting classes provided by Spring, then you'll find it a good choice. If, however, you are happy to implement these yourself (or rely on external projects to provide them) then Pico's small footprint might be a deciding factor >>>, keep in mind that the Spring distro comes with jars containing different levels of functionality. If all you need is the BeanFactory support, there is a spring jar with that code, which is not significantly larger that Pico with XML config support... For a slightly larger footprint, you can use the Spring version which adds ApplicationContext support (for AOP and related functionality). As such, I would make decisions on using Spring vs. Pico on other aspects than footprint size...
Also, note that Spring 1.0RC1, due out in a few days, plays around with the packaging variations compared to the current 1.0M4. It doesn't however change anything w/regards to what I said in my note above.
Small footprint
2004-02-10 08:04:47 samnewman
[Reply | View]
Thanks for the clarification. One of the problems with this piece was the pace of change wrt Pico and Spring. I tried to make it as accurate as possible, but I was only using the latest packaged JAR's rather than the bleeding edge CVS versions. Since I finished the finally draft even more things have changed - I may well do a complete rewrite in a few months once we have a Spring 1.0 and a PicoContaoner/NanoConainer 1.0 to look at!
Avalon, IoC, and other fun
2004-02-10 07:08:33 jaaron
[Reply | View]
Wow, there's sure been a plethera of IoC articles going around. I don't completely agree with your analysis of Avalon (though being an avalon developer, I may be somewhat biased). There are advantages and disadavantages to each approach.
I would also like to point out that IoC means a lot more than just dependency management. It's a principle which covers many design patterns, dependency injection being simply one. For more information, see some of the links below:
re: Avalon, IoC, and other fun
2004-02-10 07:15:12 samnewman
[Reply | View]
The gist of why I prefer picoContainer or Spring over Avalon is purely because I dislike coupling my application code to supporting frameworks. I do appreciate that IoC is also used for lifecycle management and configuration control, however the underlying principle remains the same - don't call us, we'll call you. I will be doing and article looking at one of the IoC containers in more depth to explore some of these other uses - when I find time!