Skip to main content

IoC Container Face-Off

February 10, 2005

{cs.r.title}









Contents
Terminology
The Problem Domain
The Service Locator Pattern
The IoC Pattern, Defined
Dependency Injection Types
PicoContainer
HiveMind
Some Final Thoughts

Within the web world, you won't find too many web applications that
aren't built on top of the likes of Model View Controller, Business
Delegate, Session Facade, Data Access Object, or other patterns
these days. These patterns have been used to form architectures that
attempt to provide a stronger foundation for our applications. By
utilizing some of these patterns together, we avoid the problems faced
by past development efforts, and provide extensibility for future growth.
However, one problem still remains: component dependency resolution.

In this article, we will take a look at the problem in more depth
and learn how others have tried to solve the problem by utilizing
frameworks that implement the Inversion of Control (or IoC) pattern.
First, we'll become familiar with some terms, the IoC pattern, and
other patterns that have tried to implement a solution (but didn't
completely succeed). Then we'll move on, to see how two of the
most popular IoC frameworks are used today. These frameworks are
PicoContainer and
HiveMind.

Terminology

Throughout the remainder of this article, we'll refer to components,
services, and classes interchangeably. In a more realistic setting,
components could mean self-reliant pieces of code that are made up
of various classes in order to provide a local reusable service. You
could also say that the word "service" is used to refer to a remote
component (as is the case with web services today).

I don't want you to become hung up on these terms while trying to
understand the concepts you're about to meet, since these terms change
from time to time to mean different things. Regardless of what you
are trying to connect, the problem still remains, and hopefully, the
solution will still hold as well.

In the examples you are about to see, the pieces we're trying to connect
are simple classes, but they could be replaced with more complicated
components, services, or objects. You'll see me refer to some of these
classes as services or even components, but in the end, they are just
simple classes used within the example to make the point stick.

Another term you will see is "bootstrap." This normally refers to a piece
of code that connects various objects together prior to being used by
the actual application or system.

Finally, you should become familiar with the term "container" (a term that has been overloaded tenfold) as used in
this context. Here, we're
referring to an object that holds other objects. This can be a simple
component or class, or something more complicated, such as a framework.

The Problem Domain

In every application that you will ever have to build, there will be
components that need to communicate with each other. For example, you might
have to build a front end that communicates with the user and manages the
presentation, navigation, and interaction. There may also be some middle-tier code that communicates with data stores, message services, or mainframes,
and returns the retrieved data to the front-end code. Furthermore, the
back-end pieces that actually communicate with the above mentioned subsystems
will also have to be built and connected to the rest of the system.

Let's look at an example of this kind of programming in Java. First, let's
set the stage. Imagine that we have a component named DomainStore (which
implements the Domain Store pattern). The purpose of this component is to
abstract the caller, who wishes to persist some data to a persistent data
store. Keep in mind that our best practices dictate that the caller should
not have to care where the final destination will be. The underlying
DataStore should utilize an appropriate implementation for storing or loading
the data.

The actual underlying storage could be a local file, a database, or even a
message queue. The point is that the caller shouldn't have to be concerned about
what the underlying data store will be. In the real world, we all know that
at some point, someone will have to decide what the actual data store will
be, but this can either be chosen dynamically, or through a configuration file.

Before we look at the DomainStore implementation, let's first look at the interface
that the DomainStore will need to communicate with when attempting to pass
data or read data from the underlying data store. The interface (as defined in
Listing 1) implements the Data Access Object (or DAO) pattern we've come to know
and love by now. There are the usual create, read, update, and delete methods
(CRUD, as it's humbly come to be known as).

Listing 1. The DataAccessObject interface definition

package integrationtier;

public interface DataAccessObject {

    void create(Object dataObject);

    Object read(String criteria);

    void update(String criteria,
    Object dataObject);

    void delete(String criteria);
}

The actual implementation for this interface could come in many different forms.
It could be implemented as a database-aware object utilizing JDBC to communicate
with the database. It could be defined to use files and streams to communicate
with local files or files on a network. Or, it could be implemented using JMS
to communicate with a message queue.

Listing 2 and Listing 3 provide two implementations. The first one provides a
local file implementation and the second a database access implementation. Of
course, I didn't create the actual code within the methods to do either, but
you can tell by my comments what would actually be performed within those
methods. Remember the point here: it shouldn't matter what the classes do to
achieve their work, just that the implementation will be different. Given this
fact, I could design the interface and provide one or two implementations of
the interface, and then have others provide more implementations. Because the
DomainStore uses the DataAccessObject interface to
communicate with the data store, it doesn't care what the underlying implementation
does.

Listing 2. The FileDataAccessObject class implementation

package integrationtier;

public class FileDataAccessObject implements
  DataAccessObject {

  public void create(Object dataObject) {
// This implementation performs the
// following steps:
//  1. Open the file
//  2. Go to the end of the file.
//  3. Serialize the content of the data
//     object to the file.
//  4. Close the file.
  }

  public Object read(String criteria) {
// This implementation performs the
// following steps:
//  1. Open the file.
//  2. Continue to read data from the
//     file until we reach the
//     record we're looking for.
//  3. Serialize in the record and reconstruct
//     an object.
//  4. Close the file
//  5. Return the object to the caller.
return null;
  }

  public void update(String criteria,
    Object dataObject) {
// This implementation performs the following
// steps:
//  1. Open the file.
//  2. Continue to read data from the file
//     until we reach the
//     record we're looking for. Use the
//     criteria to find the record.
//  3. Serialize out the record from the
//     passed in object
//     at that location.
//  4. Close the file
  }

  public void delete(String criteria) {
// This implementation performs the following
// steps:
//  1. Open the file.
//  2. Read the entire content of the file.
//  3. Serialize out the record from the
//     passed in object
//     at that location.
//  4. Close the file
  }

}

Listing 3. The DbDataAccessObject class implementation

package integrationtier;

public class DbDataAccessObject implements
  DataAccessObject {

public void create(Object dataObject) {
// 1. Access a connection to the database
// 2. Construct an SQL statement
// 3. Execute the SQL statement using
//    data from the DataObject.
// 4. Close the database objects.
}

public Object read(String criteria) {
// 1. Access a connection to the database
// 2. Construct an SQL statement
// 3. Execute the SQL statement (receive a
//    resultset)
// 4. Place the data from the result set
//    into the DataObject.
// 5. Close the database objects.
return null;
}

public void update(String criteria,
  Object dataObject) {
// 1. Access a connection to the database
// 2. Construct an SQL statement
// 3. Execute the SQL statement using data
//    from the DataObject
//    to update the database record.
// 4. Close the database objects.
}

public void delete(String criteria) {
// 1. Access a connection to the database
// 2. Construct an SQL statement
// 3. Execute the SQL statement using data
//    from the
//    Criteria string to delete the
//    respective record.
// 4. Close the database objects.
}

}

Listing 4. The DomainStore class implementation

package integrationtier;


/**
* This class provides a Data Access service. It
* is provided with the specific DataAccessObject
* it should use when storing, retrieving,
* deleting, or updating objects.
*/
public class DomainStore {
  private DataAccessObject dao = null;

  public DomainStore(DataAccessObject dao) {
this.dao = dao;
  }

  /*
   * Store a new object or update existing
   * object using this method.
   */
  public void store(String criteria,
    Object dataObject, boolean isNew) {

if(isNew)
dao.create(dataObject);
else
dao.update(criteria, dataObject);
  }

  /*
   * Retrieve an existing object.
   */
  public Object load(String criteria) {
return dao.read(criteria);
  }

  /*
   * Remove an existing object.
   */
  public void remove(String criteria) {
dao.delete(criteria);
  }
}

Listing 4 provides the DomainStore implementation. The first thing to notice about
this class is that it expects to receive a reference to an object, which implements
the DataAcessObject interface. It doesn't care what the actual implementation does.
It just knows that when its owner calls any of its methods, it will delegate the call
to a DataAccessObject implementation, which will handle the actual call and do whatever
it was built to do.

As you can see from the class definition, any decisions that need to be made prior to
calling the underlying DataAccessObject are made within the DomainStore; for example,
take a look at the store method. Also notice that the methods have slightly different
names than the underlying DataAccessObject's methods. This was done in order to provide
another level of abstraction, required in this case to abstract the client from the
underlying data store.

Now, let's take a look at the client code that would be required in order to connect
these pieces together and get some data passing back and forth. Please refer to Listing 5.

Listing 5. The client code that connects the components in this example

package dependentsolution;

import integrationtier.*;

/**
* This class creates and uses the necessary
* services directly, causing a Component
* Dependency between the various pieces.
*/
public class DependentSolutionTest {

  public static void main(String[] args) {
// Call the necessary business functionality.
performBusinessTask();
  }

  /*
   * Performs business tasks - Imagine that this
   * functionality
   * is called from or via a Business Delegate.
   */
  private static void performBusinessTask() {
// We're going to use the Database DAO
// implementation.
DataAccessObject dbDao =
  new DbDataAccessObject();

// Specify the Database DAO as its DataStore
// device.
DomainStore dbStore = new DomainStore(dbDao);

// Tell the datastore to load an object named,
// "Ken".
Object person = dbStore.load("Ken");
  }
}

As can be seen in Listing 5, the business code has to literally connect the various
pieces together by making a conscious decision that it wants to communicate with a
database through a domain store. Remember that I said that at some point, something has
to decide how these pieces are connected and which pieces are connected in order to
accomplish the task at hand. Another problem here is that I have to either keep global
objects around somewhere in my code for these components, or I have to continue to create
them as I need them. This complicates matters even further, because if I ever need to change
the code to use another type of underlying storage, I have to go through the code and refactor it.

This, of course, is a very simple example to demonstrate the problem. However, I've come
across many applications that were written in this fashion. Later, when it was time to
adopt a different component implementation, the project teams realized that there were one
or more component dependencies, which they needed to resolve. This example makes use of interfaces
and provides the functionality by implementing the interface. I've seen applications that created
classes directly with no interface in between, which of course led to more refactoring when the
time came to switch to a different implementation. Now that we've seen and hopefully understand
the problem, let's look at one possible solution.







The Service Locator Pattern

The Service Locator pattern is put in place to provide a location-independent component that
can hold onto the various services (classes, components, services, etc.) on behalf of a system
and then return those components to a caller, based on some given ID. Most service locators are
implemented as a sort of map, which uses textual strings to map a string ID to a component.

Let's walk through an example that utilizes the Service Locator pattern to provide a possible
solution to the problem outlined in the last section. In order to avoid possible refactoring
problems, I'll provide access to the ServiceLocator implementation through an interface, as
defined in Listing 6.

Listing 6. The ServiceLocator interface

package servicelocator;

/**
* This is the interface definition of the
* ServiceLocator pattern.
*/
public interface ServiceLocator {
Object locateService(String serviceName);
}

The one and only method exposed by this interface is locateService, which returns an object given
its serviceName. Listing 7 provides a possible implementation for this interface. The class uses a
java.util.HashMap object to store and retrieve the services. Notice that there are some additional
public methods exposed by this implementation. These methods would be used by a bootstrap process
to drop the components into the ServiceLocator object.

Listing 7. The ServiceLocatorImpl class definition

package servicelocator;

import java.util.HashMap;
import java.util.Map;

/**
* This is the class implementation of the
* ServiceLocator interface.
*/
public class ServiceLocatorImpl implements
  ServiceLocator {

  private Map services = new HashMap();

  private static ServiceLocator instance =
    new ServiceLocatorImpl();

  public static ServiceLocator getInstance() {
return instance;
  }

  public Object locateService(String serviceName)
  {
return services.get(serviceName);
  }

  public void addService(String svcName,
  Object service) {

services.put(svcName, service);
  }

  /**
   * Clears out all references to any services
   * presently held by the Service Locator.
   *
   */
  public void cleanup() {
services.clear();
  }
}

Another observation you might make about this class is that it implements the Singleton pattern. Some
might say that singletons are bad (especially in clustered web environments). I tend to agree with this,
since I've seen some of the problems that can crop up from singleton implementations. If you insist on
using singletons, you need to explicitly design for such challenges. Most IoC implementations avoid these
problems by off-loading the creation and destruction of the container onto the calling code, and avoiding
the Singleton pattern altogether.

The only thing left is to show how this class is used in an actual application. Listing 8 provides a
ServiceLocatorTest client. Think of the main method as the bootstrap process and the
performBusinessTask method as a method that will be called at some later point (perhaps
within a business delegate implementation).

Listing 8. The ServiceLocatorTest class definition

package servicelocator;

import integrationtier.*;

/**
* This class retrieves the service locator,
* places some objects into it, and retrieves the
* services for use.
*/
public class ServiceLocatorTest {

  public static void main(String[] args) {
/*
* Initialize the service locator.
* We'll use the actual implementation to call
* methods that would need to ONLY be called
* when the service locator is initialized
* and only from the object initializing it.
*/
ServiceLocatorImpl sl = (ServiceLocatorImpl)
ServiceLocatorImpl.getInstance();

// We're going to use the Database DAO
// implementation.
DataAccessObject dbDao =
new DbDataAccessObject();

// Place the service in the ServiceLocator,
// specifying the Database DAO as its DataStore
// device.
sl.addService("DataStore",
  new DomainStore(dbDao));

// Call the necessary business functionality.
performBusinessTask();
  }

  /*
   * Performs business tasks - Imagine that this
   * functionality is called from or via a Business
   * Delegate.
   */
  private static void performBusinessTask() {
// Retrieve the Singleton service locator
// object.
ServiceLocator sl =
  ServiceLocatorImpl.getInstance();

// Locate the service named, "DataStore".
DomainStore dbStore =
  (DomainStore)sl.locateService("DataStore");

// Tell the datastore to load an object named,
// "Ken".
Object person = dbStore.load("Ken");
  }
}

The bootstrap has to explicitly connect these pieces together by first creating the DataAccessObject
component, and then passing the component to an instance of the DomainStore object. Remember,
the DomainStore expects a DataAccessObject as its constructor's input parameter.
Finally, the DomainStore object is passed to the ServiceLocator for containment.

Inside of the performBusinessTask method, the ServiceLocator singleton is retrieved and
then put to use. The component is retrieved with a call to locateService. Notice the textual string
passed to the locator method. Once the DomainStore component is retrieved, the load method is called,
passing it a parameter. Note: the parameter could be used in a select statement if the underlying DAO is a
database object, or is just used to sequentially look up a record, in the case of a flat file.

Throughout this example, I've mentioned a number of possible issues that can be addressed by switching to an
IoC container implementation. But first, before we see an IoC example, let's learn a bit about the actual
pattern behind these types of containers.

The IoC Pattern, Defined

The Inversion of Control (or IoC) pattern was designed to solve a multitude of issues, including:

  • Component dependency resolution
  • Configuration management
  • Lifecycle management

Most IoC implementations or frameworks concentrate on component dependency resolution and lifecycle
management, leaving configuration management to a third-party component to handle. This is the case
with PicoContainer. But I will come back to this issue in the next section.

The IoC pattern is also known as Dependency Injection, which actually better describes what the
pattern does. At runtime, the implementation is said to inject a particular implementation when needed
by an object. The selection and creation of the dependent object isn't resolved until a component that
utilizes the object is requested. At this point, the container looks at what the component might need
in order to become fully initialized and assures that these prerequisites are met. In order to help us
understand this concept, let's look at a graphical representation of it in Figure 1.

Figure 1
Figure 1. Graphical representation of the Dependency Injection concept

As shown in Figure 1, the client makes a request for Component A. Component A depends on Component B
and Component C. Therefore, these two components are constructed prior to constructing Component A, since
it will need them in its own initialization before being returned to the client. If you've been following
along thus far, the question that should be going through your head at this point is:
"How does the container know which components to create before creating Component A?"

The component doesn't go out and fetch the objects it depends on. Instead, it simply declares the types.
The container is then responsible for fulfilling the order. It's sought of like going to a restaurant and
telling the waiter that you would like to have fish. You didn't tell him what kinds of fish, you simply
stated that you want fish. Therefore, the waiter can come back with cod, catfish, or any other variety
that fits the bill.

IoC works in the same way. A component such as a DomainStore can specify that it requires a
DataAccessObject, and later be passed one when it is needed. If the DomainStore
specified that it wished to use a database-aware component, it would be totally controlling the type of object it would be using. However, by allowing
the container to choose one for it, it is delegating its control (hence "Inversion of Control").

IoC frameworks can choose to implement the dependency discovery however they choose, although the obvious
choice is to use reflection to reverse-engineer the component to discover the types of objects it depends on,
and then create instances of those objects based on some registry that is kept by the framework. This will
become clearer as we meet the two IoC containers used in this article.

Dependency Injection Types

There are formally three types of dependency injection techniques implemented by IoC containers. The first
type is referred to as interface injection (AKA "Type 1 IoC"). This technique relies on interfaces for specifying
the injection class. For example, in our case, we could say that the DbDataAccessObject can be
injected in place of DataAccessObject. Later, when an object such as the DomainStore
requires a DataAccessObject, it can
be provided one based on the interfaces registered. However, this technique makes no assumptions about any of
its registered classes, as is the case with Constructor or Setter dependencies. Furthermore, it isn't passed
the object in its constructor or a setter method. Instead the component, in most cases, must request an
instance that implements the required interface.

An example of a container framework that uses interface injection is Avalon (an Apache project that was
recently announced closed). Here's an example of how the DomainStore would need to access a
DataAccessObject at runtime.

Listing 9. An Avalon example

public class DomainStore implements Serviceable {
private DataAccessObject dao = null;

public void service(ServiceManager manager) throws ServiceException {
dao = (DataAccessObject)manager.lookup("DataAccessObject");
}
}

In this case, the ServiceManager acts as a ServiceLocator. Notice the textual
string passed to the lookup method.

The second technique is known as setter injection (AKA "Type 2 IoC"). With setter injection, the component
that depends on other components must implement setter methods to receive the components that it depends upon.
In most cases, there needs to be some glue to tie the pieces together. We'll see an example of this technique
when we take a look at the HiveMind example.

The last technique, and the one I prefer, is constructor injection (AKA "Type 3 IoC"). In this technique,
the container looks at the constructor of the requested component. It then creates any necessary dependent
components and ensures that the constructor of the request component is passed the dependent components
as needed. In the next section, we'll see this technique used as we take a look at the PicoContainer example.







PicoContainer

Now that you understand what IoC is from a conceptual perspective, let's see how we can make use of it
within a specific implementation of the pattern. The container I'm referring to here is the PicoContainer (or "Pico"),
a lightweight IoC container that relies on nothing but the Java standard edition API.

Although the PicoContainer supports both setter and constructor injection techniques, its developers prefer
the constructor injection technique and suggest that developers follow suit. Another important feature of
PicoContainer is that it doesn't require the application to provide a registry or configuration file of any
type. In fact, it doesn't even support this on its own. This is done for two reasons: one, to keep
the code to a minimum, and two, to keep the dependencies low. If you wish to have a configuration file to
provide the glue, you can use the NanoContainer, which sits on top of PicoContainer and provides the
registration of objects based on a configuration file.

One other cool feature of Pico is that it can easily be pulled out of the picture and the components tied
directly together (although I can't imagine that you'd want to do that once you start to use it).

Listing 10 provides an example of a client that creates an instance of a PicoContainer. Notice that it
isn't used as a singleton; we simply keep the instance around and manage it as needed. Once the instance
is created, we simply pass it the implementation classes needed by this application. Again, imagine that the
main method is the bootstrap process. Observe the fact that we don't need to tell the container anything
about the way in which these classes are to be connected. It figures these details out on its own.

Listing 10. A PicoContainerTest client class

package picocontainer;

import org.picocontainer.*;
import org.picocontainer.defaults.*;

import integrationtier.DbDataAccessObject;
import integrationtier.DomainStore;

/**
* This class creates a default pico container,
* places some classes into it,
* and retrieves the services for use.
*/
public class PicoContainerTest {
  private static MutablePicoContainer pico = null;

  public static void main(String[] args) {
/*
* Initialize the service locator.
* We'll use the actual implementation to call
* methods that would need to ONLY be called
* when the service locator is initialized
* and only from the object initializing it.
*/

pico = new DefaultPicoContainer();

// Place the service in the Container.
// Notice that we don't have to create an
// instance or pass it to the DomainStore. The
// DomainStore will automatically be passed
// the DAO implementation.
//
// Also notice that we don't use static names
// here.
// Instead, we use the class type to identify
// the classes.
pico.registerComponentImplementation(
  DomainStore.class);
pico.registerComponentImplementation(
  DbDataAccessObject.class);

// Call the necessary business functionality.
performBusinessTask();
  }

  /*
   * Performs business tasks - Imagine that this
   * functionality is called from or via a
   * Business Delegate.
   */
  private static void performBusinessTask() {
// Locate an instance of the service named,
// "DataStore".
DomainStore dbStore =
(DomainStore)pico.getComponentInstance(
DomainStore.class);

// Tell the datastore to load an object named,
// "Ken".
Object person = dbStore.load("Ken");

// Look Mom! No Singletons!
  }
}

We also don't have to specify any textual ID for the registration. It simply uses the class object as the
ID, which it maps to the actual class object itself, for later retrieval. The
registerComponentImplementation
method is actually overloaded to allow the caller to simply pass the class object, pass the class object
and an ID; or pass the class object, the ID, and a set of parameters, which are then passed to the object
when the object is constructed. The fact is that the PicoContainer has many different methods to register
implementations, instances, and adaptors (too many to mention here). Just the same, there are a great
number of methods to create instances with, as well. To mention and explain all of the various methods and
parameters that can be used when communicating with PicoContainer could fill a book.

Later, in the performBusinessTask method, a DomainStore object is requested and
Pico is happy to comply.
We didn't need to pass it anything about the constructor. We didn't have to create an instance of the
DataAccessObject; it just worked. Also notice that we didn't have to pass it a textual string;
instead, we pass it the class object of the type of object we expect back from Pico. Once the
DomainStore object is returned, we're ready to call its methods.

The PicoContainer can handle very complicated references, such as when one component depends on another,
which depends on the first. It handles these situations by providing proxies. Proxies are used to supply
a component with its dependencies. The dependencies are actually proxies that are used in place of the
actual dependent object. The dependent object will be created just in time, when it is initially called.
The proxy will provide the exact interface as the dependent object; however, any calls made to the proxy
will be delegated to the actual object once it is created.

Thus far, we've seen how a single PicoContainer can be used to house all of the various services that can
possibly be used by a system. However, what if you want to limit the visible scope of one type of object
to one or more other objects? Also, what if you want the ability to have hierarchies of containers and the
ability to override objects that exist in containers at lower levels of the hierarchy?
Pico offers the ability to create hierarchies of containers, which support all of these functions. See Figure 2
for an example of how a hierarchy could be used to control the scope of objects within a given container hierarchy.

Figure 2
Figure 2. Graphical representation of a container hierarchy

PicoContainer can also assist in testing your code with its support of mock objects. In fact, PicoContainer
supports various mock objects, including
JMock,
EasyMock, and other types of
MockObjects.

All in all, this container has some major features. It's lightweight, small in size, and definitely
deserves a look at if you're about to begin a new project that has the potential to grow with many class
and/or interface dependencies.

HiveMind

HiveMind is another very popular IoC implementation. It also supports both constructor injection and
setter injection (although most of the HiveMind developers use the setter injection technique). The
premise behind HiveMind is simple: it treats all points as services. A point (or "service-point," as it's
often referred to) is an interface coupled with an implementation. The service-point specifies the
complete path to the interface along with an identifier, used by the system, other modules, or within the
same module definition to refer to that service-point.

A service-point is the identification of a service, along with all of the various pieces needed in order
to construct an instance of that service. HiveMind utilizes models in order to determine how it should
treat a service. There are three types of models in HiveMind: singleton, deferred, and threaded.

A singleton service is one that is created as a single instance and shared across all threads. A
deferred instance is treated similar to a singleton service, except that the service isn't actually
created until the first invocation to one of its methods takes place. In the threaded model, each
thread is provided with an instance of the service, and is kept in the thread local storage. Additionally,
HiveMind provides caching functionality, which can be used to house the service instances created using
the threaded model (to improve performance).

The configuration of HiveMind-based services takes place in an XML file, used to specify the various
service points of the system. Take a look at Listing 11 for an example.

Listing 11. The HiveMindTest configuration file

<?xml version="1.0"?>

<module id="integrationtier" version="1.0.0">
    <service-point id="DbDataAccessObject"
      interface="integrationtier.DataAccessObject">
        <create-instance
          class="integrationtier.DbDataAccessObject"/>
        <interceptor
          service-id="hivemind.LoggingInterceptor"/>
    </service-point>

    <service-point id="DomainStore"
      interface="integrationtier.DomainStoreInterface">
    <invoke-factory>
      <construct
        class="integrationtier.DomainStore2"/>
    </invoke-factory>
    </service-point>
</module>

You can probably make out what the service-point and create-instance tags do.
However, notice that in this example, I used a different DomainStore. It is named
DomainStore2. This class was necessary, since HiveMind uses interfaces to describe the services.
I created an interface named DomainStoreInterface and
implemented it using DomainStore2. The invoke-factory and construct tags were used
to tell HiveMind we want
to use a factory when creating DomainStore2 objects, and we provided it the name of the class
to instantiate.
Listing 12 and Listing 13 provide the code needed to implement the DomainStore2 class. It's not much
different than before, except that it now implements an interface and a setter method.

Listing 12. The new DomainStoreInterface

package integrationtier;

/**
* This class provides a DomainStore interface.
*/
public interface DomainStoreInterface {
  /*
   * Store a new object or update existing object
   * using this method.
   */
  void store(String criteria, Object dataObject,
    boolean isNew);

  /*
   * Retrieve an existing object.
   */
  Object load(String criteria);

  /*
   * Remove an existing object.
   */
  void remove(String criteria);
}

Listing 13. The new DomainStore2 implementation class

package integrationtier;

/**
* This class provides a DomainStore
* implementation
* based on the DomainStoreInterface.
* It is provided with the specific
* DataAccessObject it should use when storing,
* retrieving, deleting, or updating objects.
*/
public class DomainStore2 implements
  DomainStoreInterface {

  private DataAccessObject dao = null;

  public DomainStore2() {
  }

  public void setDataAccessObject(
  DataAccessObject dao) {

this.dao = dao;
  }

  /*
   * Store a new object or update existing object
   * using this method.
   */
  public void store(String criteria,
    Object dataObject, boolean isNew) {

if(isNew)
dao.create(dataObject);
else
dao.update(criteria, dataObject);
  }

  /*
   * Retrieve an existing object.
   */
  public Object load(String criteria) {
return dao.read(criteria);
  }

  /*
   * Remove an existing object.
   */
  public void remove(String criteria) {
dao.delete(criteria);
  }
}

Listing 14 provides a class definition to test HiveMind. The bootstrap code (within the main method)
begins by creating the necessary objects before processing the XML file.

Listing 14. The HiveMindTest class definition

package hivemind;

import java.util.Locale;

import integrationtier.DomainStoreInterface;

import org.apache.hivemind.ClassResolver;
import org.apache.hivemind.Registry;
import org.apache.hivemind.impl.
  DefaultClassResolver;

import org.apache.hivemind.impl.RegistryBuilder;
import org.apache.hivemind.util.FileResource;

/**
* This class creates a HiveMind Registry,
* initializes it, and retrieves the services for
* use.
*/
public class HiveMindTest {
  private static Registry registry = null;

  public static void main(String[] args) {
    // Initialize the registry.
    RegistryBuilder builder =
      new RegistryBuilder();

    ClassResolver resolver =
      new DefaultClassResolver();

    // Process standard files, on the
    // classpath.
    builder.processModules(resolver);

    // Process the examples.xml file, which
    // (given its non-standard name) is not
    // visible.
    builder.processModule(resolver,
      new FileResource(
        "hivemind\\HiveMindTest.xml"));

    registry = builder.constructRegistry(
      Locale.getDefault());

    performBusinessTask();
  }

  /*
   * Performs business tasks - Imagine that this
   * functionality is called from or via a
   * Business Delegate.
   */
  private static void performBusinessTask() {
    // Locate an instance of the service
    // named, "DataStore".
    DomainStoreInterface dbStore =
      (DomainStoreInterface)
      registry.getService(
        DomainStoreInterface.class);

    // Tell the datastore to load an object
    // named, "Ken".
    Object person = dbStore.load("Ken");
  }
}

Once the builder is created, the XML file path is passed to it so that it can process and dissect it.
The result is a registry object, which we keep and later use in the performBusinessTask method.
The performBusinessTask method appears very similar to that of the PicoContainer example, retrieving
the service and then calling its load method.

As I mentioned before, HiveMind has the ability to pool threaded services. Additionally, these services
can listen to events, which tell them when they are pulled out of the pool or put back into the pool.
HiveMind also has the concept of an interceptor, which allow developers to wrap code around an object.
For example, we used the HiveMind logging interceptor in the example above. This interceptor is called
each time a method of the associated interface is called to log any exceptions that may have occurred
automatically; no need to write that code yourself. As a result, your code will be cleaner, leaner, and
free of logging code.

You can also create your own interceptors and wrap other services up with them. This allows you to add
code to existing services without modifying the service itself. This is similar to the AOP concepts
you've probably heard of by now.

One final point about the HiveMind framework is that it relies on many third-party class libraries to
accomplish its work (whereas PicoContainer is self-reliant). This is both a good point and a bad point
at the same time. What's good about it is that it reuses code instead of redesigning and rebuilding the
wheel. That has always been the idea behind object-oriented programming, right? However, the bad side is
that you'll have to figure out the best way of deploying the various libraries, which means that you're
also at the mercy of those developers to provide updates, enhancements, and patches (as needed). That
said, I was quite impressed with the lineup of libraries they chose. It includes
commons-logging,
Jakarta-oro, and the
javaassist library.

Some Final Thoughts

My intentions in this article we're to make you aware of what IoC is all about and to provide you with
a different take on the matter. Additionally, I wanted to show you some of the advantages and disadvantages
offered by two of the most popular IoC frameworks. I hope I've accomplished that. In future articles, I
will single out these frameworks, and provide the details of each. We'll go through the individual features
of each, see some example code, meet the internal architecture, and more. Hopefully, this will help you
understand when and where you would use one or the other. At this point, I don't particularly have a
favorite. If you do, I'd like to hear what it is and why you chose it.

If you wish to learn more about these technologies, I would advise you to take a look at the following
articles and/or sites:

www.martinfowler.com/articles/injection.html

www.picocontainer.org

jakarta.apache.org/hivemind

If you are interested in finding out when I write other articles about any of the technologies you've
seen here, please visit my weblog from time to time, or add it as an RSS feed. You can find it at:

weblogs.java.net/blog/ken_ramirez.

Example code shown in this article is available for download in the following zip file: examples.zip

width="1" height="1" border="0" alt=" " />
Ken Ramirez has 18 years of experience providing development services, consulting, and training to companies (both large and small) throughout the United States.