I'm an avid mountain biker. Some mountain bikers love hardcore equipment -- behemoths that are fully suspended (with huge shocks in the front and back) -- and push them to unnatural limits. I ride in the hill country around Austin, Texas, where the many rocky ledges mean I've got to have a suspended bike, but a behemoth won't work for me. I can't make it up the grueling climbs with a lot of extra weight. I've got to have a light bike.
The Java industry has gone through a few similar fads. EJB technology provided hardcore enterprise services. If you programmed to a complex component-based model, you could put your business components into a container that would provide services such as transactions, remoting, security, and persistence.
There was a cost, though. The heavyweight architectures were overkill for many problems. For example, entity beans made you code up to seven files for each entity. EJB technology just wasn't worth all the extra effort it took to solve everyday problems. Today, businesses still need the enterprise services, but they're looking in a new direction to get them. They're using lightweight containers. In fact, the new EJB V3.0 standard uses the lightweight container model.
What are lightweight containers?
Most container APIs, like EJB APIs, force you to code to some interface or a component model. You put your component into the container, and the container does something for you. EJB containers provide enterprise services. Servlet containers, such as Apache Jakarta Tomcat, implement the Servlet API, letting you build dynamic content into pages on the server that can then be sent to a Web browser.
Where traditional containers force a given programming model, lightweight containers do not. They let you insert plain old Java objects (POJOs). The container can then wire the POJOs together and attach services to them. Common characteristics of lightweight containers include:
- POJO-based programming -- Lightweight containers are not invasive. The containers do not enforce any API.
- Life-cycle management -- Lightweight containers manage the life cycle of the objects you put into them. Minimally, they instantiate and destroy objects.
- Dependency resolution -- Lightweight containers provide a common dependency-resolution strategy. Major containers today support a strategy called dependency injection. Some support a Java 2 Platform, Enterprise Edition (J2EE)-style strategy called service locators, as well.
- Consistent configuration -- Lightweight containers are a convenient place to provide consistent configuration services.
- Service attachment -- Lightweight containers provide a way to attach services to the objects in the container.
Lightweight containers have many advantages over other container architectures. For example, you get to use a simpler programming model based on POJOs. Because you're programming with POJOs, your applications are easier to test. Your objects can also run outside the container -- for example, in a test case. Through dependency injection, lightweight containers reduce dependencies between components. They also protect your investment in code because you can move more of your application between containers.
A rapid wave of innovation fuels the lightweight container movement. Dependency management drove the movement in the beginning. Early lightweight containers, such as Apache's Avalon, used the service locator strategy to manage dependencies. All the major modern containers now manage dependencies with dependency injection.
However, resolving dependencies is only part of the problem. You also need to be able to attach services to the POJOs in the container. EJB containers handled this problem with code generation. Modern containers use aspect-oriented programming (AOP) and interceptors.
In Java technology, dependency injection is rapidly changing the way we build applications. The concept is relatively simple: A consumer (like the Consumer class below) needs a service. You add a property to the consumer that points to the service (like the Speaker class below). Listing 1 shows an example of this concept.
Listing 1. Example of dependency injection
class Speaker {
void speak(String words) {
System.out.println(words);
}
}
class Consumer {
Speaker mySpeaker;
void saySomething() {
mySpeaker.speak("something");
}
}
|
Notice the Consumer class. It doesn't instantiate the Speaker class. With dependency injection, that job goes to a third party -- call it the Container class (see Listing 2).
Listing 2. The Container class
class Container {
public static void main(String[] args) {
Speaker speaker=new Speaker();
Consumer consumer=new Consumer();
consumer.speaker = speaker;
consumer.saySomething();
}
}
|
The Container class instantiates the Speaker and Consumer classes. Then the Container class sets the speaker property to the new Speaker class. This last step represents the dependency injection.
Let's refactor this code a bit. Build an interface called Speaker, then a couple of different implementations: a Canadian speaker and a Californian speaker. So, you now have the Speaker interface:
interface Speaker {
void speak(String words);
}
|
the CanadianSpeaker and CalifornianSpeaker:
class CanadianSpeaker implements Speaker {
public void speak(String words) {
System.out.println(words + ", ay?");
}
}
class CalifornianSpeaker implements Speaker {
public void speak(String words) {
System.out.println("Like, " + words);
}
}
|
and the one-line change in the container:
... Speaker speaker=new CalifornianSpeaker(); |
Notice that you can now change between implementations of the speaker, and the only code that needs to change is the container. More to the point, you could easily inject a mock object instead of a real Speaker implementation, and you could do a test without affecting the rest of your code base.
Of course, your ultimate goal is to replace this handwritten container with one written for you. Take, for example, the Spring container. In this case, it replaces your Container class, and you can use a simple XML file that looks like the code in Listing 3.
Listing 3. XML file for the Spring container
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="mySpeaker" class="CanadianSpeaker">
</bean>
<bean id="consumer" class="Consumer">
<property name="speaker"><ref local=
"mySpeaker"/></property>
</bean>
</beans>
</code>
|
Now you can load the context, get the Consumer bean, and run it like this:
ApplicationContext ac =
new FileSystemXmlApplicationContext("context.xml");
Consumer c=(HelloWorld)ac.getBean("consumer");
c.saySomething();
|
The Spring container does the same thing yours did. It instantiated the beans and wired them together by setting the properties. Notice that the two pieces of code are completely decoupled; the interface and the container make sure that's true. You can use dependency injection to satisfy the many dependencies you run across when you're doing enterprise development, like data sources or transaction managers.
You can also see that the natural layers of an application naturally depend on each other. You might have a Web user interface (UI) view that's called by a controller, which calls a facade layer, which calls a data access object, which calls an object-relational mapper, which calls a database. These relationships are dependencies. If you can decouple them, you can more easily code, test, and maintain them.
Dependency injection lets you weave together the main layers of your application, which, in turn, lets you produce a loosely coupled application with view, model, and controller layers. But lightweight containers solve another problem. You often have concerns -- such as logging, remoting, or security -- that hit many parts of your application. EJB technology solved this problem with code generation and a container/component interface. We can do better.
You can code your crosscutting concern in one place, then use a technique called the interceptor to tie that code to methods that might need it. Say, for example, a caller wants to invoke a method called speak(). Using the interceptor strategy (see Figure 1), you create a proxy in front of the target object with the speak() method. The proxy should have a speak() method with the same interface that the original target object has. When the caller invokes the proxy, you're free to add any custom features you may require before invoking the real method.
Figure 1. Staple interceptor strategy for lightweight containers
Using interceptors, you can effectively add custom services like security, declarative transactions, and remoting to methods on your POJOs. Here's the kicker: Neither the calling code nor the invoked method needs to change at all. Further, containers like Spring come with prepackaged interceptors to perform these tasks and more.
AOP takes things one step further. With AOP, you can quickly specify all the methods that require a given service at once, often using regular expressions. AOP programmers call this collection of methods a point cut. For example, you may want to attach declarative transactions to all the methods in a facade. For methods starting with insert or update, you may want full transaction propagation; but for other methods, you may want lightweight read-only propagation. (The EJB specification defines types of transaction propagation. You just need to know that full propagation is more expensive than read-only propagation, but it's also safer and necessary for certain types of updates.)
You get the right transactional behavior, in the right place, without modifying any code in the caller, or the target method. Listing 4 shows part of a Spring context that specifies the point cuts for such an application.
Listing 4. Code to specify point cuts in a Spring container
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
|
You can well imagine that many people would try to build lightweight containers. The lightweight container space is rapidly consolidating around a few players: Spring, Pico, HiveMind, and EJB technology.
Leading the charge is the Spring Framework. Spring uses XML configuration and relies on setters for dependency injection. It can also use constructors or factory methods, but most examples you'll see -- and the Spring code base itself -- focus on setters.
Spring goes beyond its lightweight container by adding a whole host of beans with glue code. With the beans and code, you can drop in hundreds of different kinds of components that let you use J2EE APIs, persistence engines from JDO to Hibernate, workflow engines, view technologies, and dozens of other things. Spring is rapidly maturing and has the critical mass to be a player for the foreseeable future.
The Pico container is the smallest available. The primary differences between Pico and Spring relate to style. Pico programmers rely first on constructors to do injection. Instead of XML, Pico uses Java code to register beans in the container. Like Spring, Pico also supports setter injection, but the emphasis is strongly on constructors. Pico doesn't have nearly the amount of support that Spring has. It's primarily a dependency injection container. However, Pico is an excellent choice when you don't need all the enterprise services Spring provides.
HiveMind is the newest of the open source lightweight containers. HiveMind has more supporting modules than Pico does, but not nearly as many as Spring has. HiveMind does, however, let you use Spring beans and services. It can manage dependencies with both setters and constructors. HiveMind's primary contributions are two important ideas:
- A packaging concept called modules, loosely based on the Eclipse plug-in model, lets you manage dependencies at a coarse-grained level.
- A documentation facility called HiveDoc lets you generate reference documentation for the modules in the container, much like Javadoc lets you generate documentation for your Java code.
It's too early to tell whether HiveMind will have a major impact.
Think about the things you do with EJB technology today, such as declarative transactions, remoting, and security. If you could do most of what you do in EJB environments with a lightweight container instead, why wouldn't you? That question is compelling enough to force a massive redesign of the core EJB architecture, and the EJB V3.0 expert group decided to do exactly that. EJB V3.0 will effectively implement a lightweight container strategy. The primary differences between the current EJB architecture and the new V3.0 architecture: The container will provide the primary services you use and you'll be able to use XML to configure the container, but EJB technology will also rely heavily on annotations for configuration.
Some good consultants have expressed concern about what they call overuse of annotations in EJB technology. I also have my reservations, but EJB technology has bigger problems. The EJB group must deliver something soon, or the lightweight containers may render EJB technology irrelevant. Customers can already do most of what they need to do with Spring. By the time EJB technology is used in volume, it may be too late.
So, now you've seen an overview of lightweight containers. You know the basic design philosophies:
- Build a container that accepts POJOs, rather than restricted components
- Use dependency injection to loosen and resolve dependencies
- Use interception and AOP to attach services to POJOs
In the next couple of articles, I'll take a more exhaustive look at the major lightweight containers and indicate where you might use each. Then I'll take a fuller look at Spring, the most popular container. I told you at the beginning of this article that I needed to lighten up my bike without sacrificing my suspension. Now you know how to lighten up your container without sacrificing enterprise services.
Happy trails!
Learn
-
Read Spring:
A Developer's Notebook, by Bruce A. Tate and Justin Gehtland (O'Reilly, 2005), to get started quickly with Spring.
-
Check out some lightweight containers, including the Spring Framework,
Pico, and HiveMind.
-
Read Martin Fowler's article on dependency injection for a good description of dependency injection and the service locator.
-
Better, Faster, Lighter Java, by Bruce A. Tate and
Justin Gehtland (O'Reilly, 2004), provides a good overview of lightweight development.
-
Pro Spring provides a more comprehensive treatment of Spring.
-
"Object-relation
mapping without the container" (developerWorks, April 2004), by Richard Hightower, shows you how to use Spring persistence with Hibernate.
-
Read "AOP@Work: AOP tools
comparison, Part 1" (developerWorks, February 2005), by Mik Kersten, for a treatment of AOP, a lightweight technique that can give you much better transparency.
-
"Lightweight development" is a huge topic, and developers throw the term around so often that it's hard to tell what it means. "Secrets of lightweight development success, Part 1: Core principles and philosophies" introduces the core principles and philosophies behind the movement.
-
Learn the basics of lightweight containers in "Secrets of lightweight development success, Part 3: The emergence of Spring."
-
In Part 4 of the series, three popular containers are compared: Spring, HiveMind, and PicoContainer.
-
Lightweight development works best with a lightweight process, but it can be tough to get a conservative company to adopt agile methodologies. Learn how you can propose and promote lightweight processes in your organization in Part 5 of this series.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Innovate your next open source development project with
IBM trial software, available for download or on DVD.
Discuss
-
Get involved in the developerWorks community by participating in developerWorks blogs.

Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released Spring: A Developer's Notebook. He spent 13 years at IBM and is now the founder of the J2Life, LLC, consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby.





