Rukas - Oh, My Blog!

友情链接

最新评论

A Brief Introduction to IoC

A Brief Introduction to IoC A Brief Introduction to IoC

by Sam Newman
02/10/2004


Contents
The Theory of IoC
IoC Frameworks
   Interface Injection (Type 1)
   Setter Injection (Type 2)
   Constructor Injection (Type 3)
IoC in Action: The Data Access Object Pattern
Spring Vs. PicoContainer
Conclusion

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.

posted on 2005-11-17 14:34 Rukas - Oh, My Blog! 阅读(258) 评论(0)  编辑 收藏 引用 所属分类: 设计模式

只有注册用户登录后才能发表评论。