Better J2EEing with Spring

by Peter Braswell
07/11/2005

Abstract

J2EE programming is becoming trickier—much trickier. J2EE has exploded into a complex network of APIs, complicating programming, and configuration. To address some of this complexity, new frameworks and methodologies are emerging. These frameworks rely heavily on a concept called IoC, or Inversion of Control. This article explores some features and benefits of this approach as it relates to, and eases, J2EE programming.

Introduction

Mark Twain said in his famous quote "...the report of my death was an exaggeration." There have been rumblings around the Net and popular geek culture of the irrecoverable complexities of the J2EE API and the imminent death of EJB as a component architecture. This is an easy position to take from the standpoint of academic or perhaps fanciful thinking, but the truth is that the J2EE/EJB API has experienced a Darwinian evolution over what once was. Those of you who have a DCOM or CORBA project under your belts know what I mean. Back in the day, the promise of the EJB component model was a welcome breath of fresh air. The fact is, there is a tremendous investment out there in "corporateville" in all things J2EE. It may feel righteous to declare that we must throw away all previous work and retool, but that kind of thinking isn't grounded in good business acumen. EJB continues to evolve and with it idioms, practices, and frameworks spring up that complement the J2EE API. I didn't really say "spring up," did I?

I earn my lunch money as a consultant building large-scale, distributed, and often, J2EE applications. As such, I have the opportunity to see many projects go through their full lifecycle. I have the added benefit of carrying what I learned right out the door of a completed engagement straight into a brand new project. In a sense my "natural selection" process is accelerated. I can say that recently Spring (and in more general terms IoC or Inversion of Control) has been creeping into my projects more and more. What I propose in this article is Spring as an enabler or enhancer for J2EE projects. Spring, quite literally, is a framework that can standardize a lot of J2EE best practices and can homogenize many ubiquitous J2EE patterns. What follows is a tour of a small part of the Spring universe highlighting those things that, in my humble opinion, can help make your J2EE applications better J2EE apps.

A Brief Introduction to What It Means to IoC

In general, IoC is a technique for managing associations between classes. Yes, it's that simple! No person is an island and the same is true for individual objects. Objects in applications depend on other objects. Rendering this programmatically is often tedious and error-prone. A good IoC framework will assist you in stitching together the dependencies of your application declaratively (via an XML configuration file), not programmatically, which tends to be brittle.

The liberal use of interfaces is a mantra of IoC development. Programming to interfaces augments declarative associations in a way that adds tremendous flexibility to your application. Interface implementation is declared at runtime through your IoC configuration, allowing associations to be "rewired" with little or no impact to the actual application code. This is a recurring theme in the various IoC frameworks and, in general, is a good practice to follow.

A Small Example

I tend to gain an understanding of concepts more quickly by looking at examples. What follows is a series of examples that utilize IoC; as you'll see, the examples get progressively more complex. The way most of us start out with IoC containers is by exploiting their ability to inject dependencies—that is, to declaratively associate one object with another. Utilizing IoC helps you create cleaner code that is generally more flexible and easier to rewire should that become necessary. IoC benefits extend beyond dependency injection, but extended capabilities really start with dependency injectors.

We'll start by building a simple dependency injection example. This first example showcases two concepts already mentioned. The first is IoC's ability to build and wire objects at runtime, and the second is the flexibility this yields when combined with coding to interfaces. Let's start by assuming an architect hands you the UML show in Figure 1.

Figure 1
Figure 1. Pluggability via interfaces

This small example represents a system whose purpose is to measure temperatures. Sensor objects are of several different types but all implement the ProtocolAdapterIfc interface, therefore they are interchangeable with one another as they are plugged into the TemperatureSensor object. When a TemperatureSensor is needed, some entity in the system must know what concrete type of ProtocolAdapterIfc to produce and associate with the sensor object. In this trivial case, our sensor could be configured based on a command-line argument, a row in a database, or from a property file. This example isn't really interesting enough to pose a challenge or justify a complex framework, but it will serve well to demonstrate IoC basics.

Imagine, however, this happening many, many times in a reasonably complex application in which you want to vary object wiring dynamically or at the very least externally. Perhaps you have a DummyProtocolAdapter that always returns a value of 42 and you use this for testing purposes. Wouldn't it be nice to have a simple, unified framework that a developer could count on to orchestrate the wiring of associations among classes and do this in such a way that it is consistent, externally configured, and doesn't cause an unsightly proliferation of factory singleton classes? This may not sound like a big deal, but the power lies in IoC's simplicity.

We have a class TemperatureSensor that holds an association to a class that implements the ProtocolAdapterIfc interface. TemperatureSensor will use this delegate class to get the temperature value. As illustrated in the UML diagram, there are several classes in our application that implement ProtocolAdapterIfc and subsequently can be used in this association. We're going to use our IoC framework (in this case Spring) to declare the implementation of ProtocolAdaperIfc to be used. Spring will handle wiring the associations at runtime. First, let's have a look at the XML stanza that will instantiate a TemperatureSensor object and associate an implementation of ProtocolAdapterIfc with it. That stanza looks like this:

<bean id="tempSensor"

class="yourco.project.sensor.TemperatureSensor">

  <property name="sensorDelegate">

    <ref bean="sensor"/>

  </property>

</bean>



<!-- Sensor to associate with tempSensor -->

<bean id="sensor" class="yourco.project.comm.RS232Adapter"/>

After looking at this code, the intent should be very clear. We're configuring Spring to instantiate a TemperatureSensor object and associate an RS232Adapter as our class that implements the ProtocolAdapterIfc interface. If we want to alter the implementation that gets associated with our TemperatureSensor, the only thing that needs to be changed is the class value in the sensor bean tag. TemperatureSensor doesn't care what gets associated as long as it implements the ProtocolAdapterIfc interface.

Putting this to work inside the application is fairly straightforward. We must first access the Spring framework, next point it to the correct configuration file, and then ask Spring for an instance of our tempSensor object by name. Here's how this can be done:

ClassPathXmlApplicationContext appContext =

  new ClassPathXmlApplicationContext(

  new String[]

  { "simpleSensor.xml" });

BeanFactory bf = (BeanFactory) appContext;

TemperatureSensor ts = (TemperatureSensor)

  bf.getBean("tempSensor");

System.out.println("The temp is: "+

  ts.getTemperature());

As you can see, this is not terribly difficult. We begin by bootstrapping Spring and specifying the configuration file we want it to use. Next we reference our bean by name ( tempSensor). Spring handles the mechanics of creating that object and wiring together the objects based on what was described in the simpleSensor.xml file. It injects the dependencies for us—in this case instantiating the RS232Adapter object and associating it with the TemperatureSensor object by passing it in as a parameter to the sensorDelegate() method.

By contrast, accomplishing the same thing using programmatic Java is not terribly difficult. It would look something like this:

TemperatureSensor ts2 = new TemperatureSensor();

ts2.setSensorDelegate(new RS232Adapter());

The purists among us might argue this is actually better. There are few lines of code and these are probably more readable. While this is true, the solution is far less flexible.

  • You can swap in and out different implementations of the various objects in the various tiers at will. For example, if a component in the Web tier needs additional functionality from a new business object, all you have to do is associate the business object with the Web tier object much like we did in the TemperatureSensor example above. It will be "injected" into the Web object for immediate use.
  • This ability to reconfigure the structure of entire applications means that you can change data sources really easily, for example, or create different configuration files for different deployment scenarios, or even create more useful, different configuration files for test scenarios. In a test scenario you may inject mock objects implementing the interfaces instead of the real objects. We'll see an example of this shortly.

What has been illustrated here is perhaps the simplest form of dependency injection. Using this same strategy not only can we associate classes with other classes, but we can install properties in classes. Properties such as strings, integers, or floats can be injected into classes through the Spring configuration file as long as they have JavaBean style accessors. We can also create objects and install properties or bean references via the constructor. They syntax for this is only slightly more complicated than setting via a property.

All of this is done using a flexible, declarative form of configuration. No code changes are necessary, and all the hard work of wiring the dependencies is done for you by Spring.

Pages: 1, 2, 3

Next Page »