A Brief Introduction to IoC

来源:互联网 发布:工商网络培训心得 编辑:程序博客网 时间:2024/04/25 01:25

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 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:

JDBCDataManger dm =

    (JDBCDataManger)pico.getComponentInstance(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.

Sam Newman is a Java programmer. Check out his blog at magpiebrain.com.

View all java.net Articles.

Does IoC make code clearer or more resilient?

Showing messages 1 through 11 of 11.

·                         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]

http://www.martinfowler.com/articles/injection.html

o                                        IOC article by Martin Fowler
2004-02-20 06:02:37 samnewman [Reply | View]

I saw the article - in fact I reference and link to it in the text.

·                         deferred relations setup
2004-02-11 08:35:35 ygmarchi [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.

Is this the way? And how?

o                                        deferred relations setup
2004-02-11 09:45:16 samnewman [Reply | View]

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.

o                                        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:

http://www.jadetower.org/muses/archives/000020.html
http://www.jroller.com/page/lsd/20040122#ioc_dependency_management
http://www.betaversion.org/~stefano/linotype/news/38/
http://www.jadetower.org/spire/mutuals/
http://avalon.apache.org

o                                        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!

原创粉丝点击