IoC Container Face-Off Blog

Version 2

    {cs.r.title}



              
                              

    Contents
    Terminology
    The Problem Dom ain
    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 namedDomainStore (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 underlyingDataStore 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 DomainStorewill 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 theDataAccessObject 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 theDataAcessObject 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 aDataAccessObject 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 underlyingDataAccessObject are made within theDomainStore; for example, take a look at thestore method. Also notice that the methods have slightly different names than the underlyingDataAccessObject'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 ServiceLocatorimplementation through an interface, as defined in Listing 6.

    Listing 6. The ServiceLocatorinterface

     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 islocateService, which returns an object given itsserviceName. Listing 7 provides a possible implementation for this interface. The class uses ajava.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 theServiceLocator 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 aServiceLocatorTest client. Think of the main method as the bootstrap process and the performBusinessTaskmethod 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 theDomainStore object. Remember, theDomainStore expects a DataAccessObject as its constructor's input parameter. Finally, theDomainStore object is passed to theServiceLocator for containment.

    Inside of the performBusinessTask method, theServiceLocator singleton is retrieved and then put to use. The component is retrieved with a call tolocateService. 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 aDomainStore can specify that it requires aDataAccessObject, 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 theDbDataAccessObject can be injected in place ofDataAccessObject. Later, when an object such as theDomainStore 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 DomainStorewould 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 aServiceLocator. 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. TheregisterComponentImplementation 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, aDomainStore 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 theDataAccessObject; 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-pointand 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-factoryand 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 theDomainStore2 class. It's not much different than before, except that it now implements an interface and a setter method.

    Listing 12. The newDomainStoreInterface

     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 theperformBusinessTask method. TheperformBusinessTask 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 javaassistlibrary.

    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

      
    http://today.java.net/im/a.gif