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. 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
Ken Ramirez has 18 years of experience providing development services, consulting, and training to companies (both large and small) throughout the United States.