Wrox Home  
Search
Professional Java Development with the Spring Framework
by Rod Johnson, Juergen Hoeller, Alef Arendsen, Thomas Risberg, Colin Sampaleanu
July 2005, Paperback
US $39.99 Add To Cart


Excerpt from Professional Java Development with the Spring Framework

Why Use the Spring Framework?

The Spring Framework is an open source application framework that aims to make J2EE development easier. Unlike single-tier frameworks, such as Struts or Hibernate, Spring aims to help structure whole applications in a consistent, productive manner, pulling together best-of-breed single-tier frameworks to create a coherent architecture.

Problems with the Traditional Approach to J2EE

Since the widespread implementation of J2EE applications in 1999/2000, J2EE has not been an unqualified success in practice. While it has brought a welcome standardization to core middle-tier concepts such as transaction management, many — perhaps most — J2EE applications are over-complex, take excessive effort to develop, and exhibit disappointing performance. While Spring is applicable in a wide range of environments — not just server-side J2EE applications — the original motivation for Spring was the J2EE environment, and Spring offers many valuable services for use in J2EE applications.

Experience has highlighted specific causes of complexity and other problems in J2EE applications. (Of course, not all of these problems are unique to J2EE!) In particular:

  • J2EE applications tend to contain excessive amounts of "plumbing" code. Many code reviews repeatedly reveal a high proportion of code that doesn't do anything: JNDI lookup code, Transfer Objects, try/catch blocks to acquire and release JDBC resources. . . . Writing and maintaining such plumbing code proves a major drain on resources that should be focused on the application's business domain.
  • Many J2EE applications use a distributed object model where this is inappropriate. This is one of the major causes of excessive code and code duplication. It's also conceptually wrong in many cases; internally distributed applications are more complex than co-located applications, and often much less performant. Of course, if your business requirements dictate a distributed architecture, you need to implement a distributed architecture and accept the trade-off that incurs (and Spring offers features to help in such scenarios). But you shouldn't do so without a compelling reason.
  • The EJB component model is unduly complex. EJB was conceived as a way of reducing complexity when implementing business logic in J2EE applications; it has not succeeded in this aim in practice.
  • EJB is overused. EJB was essentially designed for internally distributed, transactional applications. While nearly all non-trivial applications are transactional, distribution should not be built into the basic component model.
  • Many "J2EE design patterns" are not, in fact, design patterns, but workarounds for technology limitations. Overuse of distribution, and use of complex APIs such as EJB, have generated many questionable design patterns; it's important to examine these critically and look for simpler, more productive, approaches.
  • J2EE applications are hard to unit test. The J2EE APIs, and especially, the EJB component model, were defined before the agile movement took off. Thus their design does not take into account ease of unit testing. Through both APIs and implicit contracts, it is surprisingly difficult to test applications based on EJB and many other J2EE APIs outside an application server. Yet unit testing outside an application server is essential to achieve high test coverage and to reproduce many failure scenarios, such as loss of connectivity to a database. It is also vital to ensuring that tests can be run quickly during the development or maintenance process, minimizing unproductive time waiting for redeployment.
  • Certain J2EE technologies have simply failed. The main offender here is entity beans, which have proven little short of disastrous for productivity and in their constraints on object orientation.

The traditional response to these problems has been to wait for tool support to catch up with the J2EE specifications, meaning that developers don't need to wrestle with the complexity noted above. However, this has largely failed. Tools based on code generation approaches have not delivered the desired benefits, and have exhibited a number of problems of their own. In this approach, you might generate all those verbose JNDI lookups, Transfer Objects, and try/catch blocks.

In general, experience has shown that frameworks are better than tool-enabled code generation. A good framework is usually much more flexible at runtime than generated code; it should be possible to configure the behavior of one piece of code in the framework, rather than change many generated classes. Code generation also poses problems for round-tripping in many cases. A well-conceived framework can also offer a coherent abstraction, whereas code generation is typically just a shortcut that fails to conceal underlying complexities during the whole project lifecycle. (Often complexities will re-emerge damagingly during maintenance and troubleshooting.)

A framework-based approach recognizes the fact that there is a missing piece in the J2EE jigsaw: the application developer's view. Much of what J2EE provides, such as JNDI, is simply too low level to be a daily part of programmer's activities. In fact, the J2EE specifications and APIs can be judged as far more successful, if one takes the view that they do not offer the developer a programming model so much as provide a solid basis on which that programming model should sit. Good frameworks supply this missing piece and give application developers a simple, productive, abstraction, without sacrificing the core capability of the platform.

Using J2EE "out of the box" is not an attractive option. Many J2EE APIs and services are cumbersome to use. J2EE does a great job of standardizing low-level infrastructure, solving such problems as how can Java code access transaction management without dealing with the details of XA transactions. But J2EE does not provide an easily usable view for application code.

That is the role of an application framework, such as Spring.

Recognizing the importance of frameworks to successful J2EE projects, many developers and companies have attempted to write their own frameworks, with varying degrees of success. In a minority of cases, the frameworks achieved their desired goals and significantly cut costs and improved productivity. In most cases, however, the cost of developing and maintaining a framework itself became an issue, and framework design flaws emerged. As the core problems are generic, it's much preferable to work with a single, widely used (and tested) framework, rather than implement one in house. No matter how large an organization, it will be impossible to achieve a degree of experience matching that available for a product that is widely used in many companies. If the framework is open source, there's an added advantage in that it's possible to contribute new features and enhancements that may be adopted. (Of course it's possible to contribute suggestions to commercial products, but it's typically harder to influence successful commercial products, and without the source code it's difficult to make equally useful contributions.) Thus, increasingly, generic frameworks such as Struts and Hibernate have come to replace in-house frameworks in specific areas.

The Spring Framework grew out of this experience of using J2EE without frameworks, or with a mix of in-house frameworks. However, unlike Struts, Hibernate, and most other frameworks, Spring offers services for use throughout an application, not merely in a single architectural tier. Spring aims to take away much of the pain resulting from the issues in the list we've seen, by simplifying the programming model, rather than concealing complexity behind a complex layer of tools.

Spring enables you to enjoy the key benefits of J2EE, while minimizing the complexity encountered by application code.

The essence of Spring is in providing enterprise services to Plain Old Java Objects (POJOs). This is particularly valuable in a J2EE environment, but application code delivered as POJOs is naturally reusable in a variety of runtime environments.

Spring in Context

Spring is a manifestation of a wider movement. Spring is the most successful product in what can broadly be termed agile J2EE.

Technologies

While Spring has been responsible for real innovation, many of the ideas it has popularized were part of the zeitgeist and would have become important even had there been no Spring project. Spring's greatest contribution — besides a solid, high quality, implementation — has been its combination of emerging ideas into a coherent whole, along with an overall architectural vision to facilitate effective use.

Inversion of Control and Dependency Injection

The technology that Spring is most identified with is Inversion of Control (or IoC), and specifically the Dependency Injection flavor of IoC. Spring is often thought of as an Inversion of Control container, although in reality it is much more.

Inversion of Control is best understood through the term the "Hollywood Principle," which basically means "Don't call me, I'll call you." Consider a traditional class library: Application code is responsible for the overall flow of control, calling out to the class library as necessary. With the Hollywood Principle, framework code invokes application code, coordinating overall workflow, rather than application code invoking framework code.

IoC is a broad concept, and can encompass many things, including the EJB and Servlet model, and the way in which Spring uses callback interfaces to allow clean acquisition and release of resources such as JDBC Connections.

Spring's flavor of IoC for configuration management is rather more specific. Consequently, Martin Fowler, Rod Johnson, Aslak Hellesoy, and Paul Hammant coined the name Dependency Injection in late 2003 to describe the approach to IoC promoted by Spring, PicoContainer, and HiveMind-the three most popular lightweight frameworks.

Dependency Injection is based on Java language constructs, rather than the use of framework-specific interfaces. Instead of application code using framework APIs to resolve dependencies such as configuration parameters and collaborating objects, application classes expose their dependencies through methods or constructors that the framework can call with the appropriate values at runtime, based on configuration.

Dependency Injection is a form of push configuration; the container "pushes" dependencies into application objects at runtime. This is the opposite of traditional pull configuration, in which the application object "pulls" dependencies from its environment. Thus, Dependency Injection objects never load custom properties or go to a database to load configuration — the framework is wholly responsible for actually reading configuration.

Push configuration has a number of compelling advantages over traditional pull configuration. For example, it means that:

  • Application classes are self-documenting, and dependencies are explicit. It's merely necessary to look at the constructors and other methods on a class to see its configuration requirements. There's no danger that the class will expect its own undocumented properties or other formats.
  • For the same reason, documentation of dependencies is always up-to-date.
  • There's little or no lock-in to a particular framework, or proprietary code, for configuration management. It's all done through the Java language itself.
  • As the framework is wholly responsible for reading configuration, it's possible to switch where configuration comes from without breaking application code. For example, the same application classes could be configured from XML files, properties files, or a database without needing to be changed.
  • As the framework is wholly responsible for reading configuration, there is usually greater consistency in configuration management. Gone are the days when each developer will approach configuration management differently.
  • Code in application classes focuses on the relevant business responsibilities. There's no need to waste time writing configuration management code, and configuration management code won't obscure business logic. A key piece of application plumbing is kept out of the developer's way.

Developers who try Dependency Injection rapidly become hooked. These advantages are even more apparent in practice than they sound in theory.

Spring supports several types of Dependency Injection, making its support more comprehensive than that of any other product:

  • Setter Injection: The injection of dependencies via JavaBean setter methods. Often, but not necessarily, each setter has a corresponding getter method, in which case the property is set to be writable as well as readable.
  • Constructor Injection: The injection of dependencies via constructor arguments.
  • Method Injection: A more rarely used form of Dependency Injection in which the container is responsible for implementing methods at runtime. For example, an object might define a protected abstract method, and the container might implement it at runtime to return an object resulting from a container lookup. The aim of Method Injection is, again, to avoid dependencies on the container API.

Uniquely, Spring allows all three to be mixed when configuring one class, if appropriate.

Enough theory — take look at a simple example of an object being configured using Dependency Injection.

Assume that there is an interface — in this case, Service — which callers will code against. In this case, the implementation will be called ServiceImpl. However, of course the name is hidden from callers, who don't know anything about how the Service implementation is constructed.

Assume that this implementation of Service has two dependencies: an int configuration property, setting a timeout; and a DAO that it uses to obtain persistent objects.

With Setter Injection you can configure ServiceImpl using JavaBean properties to satisfy these two dependencies, as follows:

public class ServiceImpl implements Service {
 private int timeout;
 private AccountDao accountDao;

   public void setTimeout(int timeout) {
     this.timeout = timeout;
   }

   public void setAccountDao(AccountDao accountDao) {
     this.accountDao = accountDao;
   }

With Constructor Injection, you supply both properties in the Constructor, as follows:

public class ServiceImpl implements Service {
 private int timeout;
 private AccountDao accountDao;

   public ServiceImpl (int timeout, AccountDao accountDao) {
       this.timeout = timeout;
       this.accountDao = accountDao;
   }

Either way, the dependencies are satisfied by the framework before any business methods on ServiceImpl are invoked. (For brevity, there are business methods shown in the code fragments. Business methods will use the instance variables populated through Dependency Injection.)

This may seem trivial. You may be wondering how such a simple concept can be so important. While it is conceptually simple, it can scale to handle complex configuration requirements, populating whole object graphs as required. It's possible to build object graphs of arbitrary complexity using Dependency Injection. Spring also supports configuration of maps, lists, arrays, and properties, including arbitrary nesting.

As an IoC container takes responsibility for object instantiation, it can also support important creational patterns such as singletons, prototypes, and object pools. For example, a sophisticated IoC container such as Spring can allow a choice between "singleton" or shared objects (one per IoC container instance) and non-singleton or "prototype" instances (of which there can be any number of independent instances).

Because the container is responsible for satisfying dependencies, it can also introduce a layer of indirection as required to allow custom interception or hot swapping. (In the case of Spring, it can go a step farther and provide a true AOP capability. Thus, for example, the container can satisfy a dependency with an object that is instrumented by the container, or which hides a "target object" that can be changed at runtime without affecting references. Unlike some IoC containers and complex configuration management APIs such as JMX, Spring does not introduce such indirection unless it's necessary. In accordance with its philosophy of allowing the simplest thing that can possibly work, unless you want such features, Spring will give you normal instances of your POJOs, wired together through normal property references. However, it provides powerful indirection capabilities if you want to take that next step.

Spring also supports Dependency Lookup: another form of Inversion of Control. This uses a more traditional approach, similar to that used in Avalon and EJB 1.x and 2.x, in which the container defines lifecycle callbacks, such as setSessionContext(), which application classes implement to look up dependencies. Dependency Lookup is essential in a minority of cases, but should be avoided where possible to minimize lock-in to the framework. Unlike EJB 2.x and Avalon, Spring lifecycle callbacks are optional; if you choose to implement them, the container will automatically invoke them, but in most cases you won't want to, and don't need to worry about them.

Spring also provides many hooks that allow power users to customize how the container works. As with the optional lifecycle callbacks, you won't often need to use this capability, but it's essential in some advanced cases, and is the product of experience using IoC in many demanding scenarios.

The key innovation in Dependency Injection is that it works with pure Java syntax: no dependence on container APIs is required.

Dependency Injection is an amazingly simple concept, yet, with a good container, it's amazingly powerful. It can be used to manage arbitrarily fine-grained objects; it places few constraints on object design; and it can be combined with container services to provide a wide range of value adds.

You don't need to do anything in particular to make an application class eligible for Dependency Injection — that's part of its elegance. In order to make classes "good citizens," you should avoid doing things that cut against the spirit of Dependency Injection, such as parsing custom properties files. But there are no hard and fast rules. Thus there is a huge potential to use legacy code in a container that supports Dependency Injection, and that's a big win.

Aspect-Oriented Programming

Dependency Injection goes a long way towards delivering the ideal of a fully featured application framework enabling a POJO programming model. However, configuration management isn't the only issue; you also need to provide declarative services to objects. It's a great start to be able to configure POJOs-even with a rich network of collaborators-without constraining their design; it's equally important to be able to apply services such as transaction management to POJOs without them needing to implement special APIs.

The ideal solution is Aspect-Oriented Programming (AOP). (AOP is also a solution for much more; besides, this article refers to a particular use of AOP, rather than the end all and be all of AOP.)

AOP provides a different way of thinking about code structure, compared to OOP or procedural programming. Whereas in OOP you model real-world objects or concepts, such as bank accounts, as objects, and organize those objects in hierarchies, AOP enables you to think about concerns or aspects in your system. Typical concerns are transaction management, logging, or failure monitoring. For example, transaction management applies to operations on bank accounts, but also to many other things besides. Transaction management applies to sets of methods without much relationship to the object hierarchy. This can be hard to capture in traditional OOP. Typically you end up with a choice of tradeoffs:

  • Writing boilerplate code to apply the services to every method that requires them: Like all cut-and-paste approaches, this is unmaintainable; if you need to modify how a service is delivered, you need to change multiple blocks of code, and OOP alone can't help you modularize that code. Furthermore, each additional concern will result in its own boilerplate code, threatening to obscure the business purpose of each method. You can use the Decorator design pattern to keep the new code distinct, but there will still be a lot of code duplication. In a minority of cases the Observer design pattern is sufficient, but it doesn't offer strong typing, and you must build in support for the pattern by making your objects observable.
  • Detype operations, through something like the Command pattern: This enables a custom interceptor chain to apply behavior such as declarative transaction management, but at the loss of strong typing and readability.
  • Choosing a heavyweight dedicated framework such as EJB that can deliver the necessary services: This works for some concerns such as transaction management, but fails if you need a custom service, or don't like the way in which the EJB specification approaches the particular concern. For example, you can't use EJB services if you have a web application that should ideally run in a web container, or in case of a standalone application with a Swing GUI. Such frameworks also place constraints on your code-you are no longer in the realm of POJOs.

In short, with a traditional OO approach the choices are code duplication, loss of strong typing, or an intrusive special-purpose framework.

AOP enables you to capture the cross-cutting code in modules such as interceptors that can be applied declaratively wherever the concern they express applies — without imposing tradeoffs on the objects benefiting from the services.

There are several popular AOP technologies and a variety of approaches to implementing AOP. Spring includes a proxy-based AOP framework. This does not require any special manipulation of class loaders and is portable between environments, including any application server. It runs on J2SE 1.3 and above, using J2SE dynamic proxies (capable of proxying any number of interfaces) or CGLIB byte code generation (which allows proxying classes, as well as interfaces). Spring AOP proxies maintain a chain of advice applying to each method, making it easy to apply services such as transaction management to POJOs. The additional behavior is applied by a chain of advice (usually interceptors) maintained by an AOP proxy, which wraps the POJO target object.

Spring AOP allows the proxying of interfaces or classes. It provides an extensible pointcut model, enabling identification of which sets of method to advise. It also supports introduction: advice that makes a class implement additional interfaces. Introduction can be very useful in certain circumstances (especially infrastructural code within the framework itself).

Here, the interest lies in the value proposition that Spring AOP provides and why it's key to the overall Spring vision.

Spring AOP is used in the framework itself for a variety of purposes, many of which are behind the scenes and which many users may not realize are the result of AOP:

  • Declarative transaction management: This is the most important out-of-the-box service supplied with Spring. It's analogous to the value proposition of EJB container-managed transactions (CMT) with the following big advantages:
    * It can be applied to any POJO.
    * It isn't tied to JTA, but can work with a variety of underlying transaction APIs (including JTA). Thus it can work in a web container or standalone application using a single database, and doesn't require a full J2EE application server.
    * It supports additional semantics that minimize the need to depend on a proprietary transaction API to force rollback.
  • Hot swapping: An AOP proxy can be used to provide a layer of indirection. (Remember the discussion of how indirection can provide a key benefit in implementing Dependency Injection?) For example, if an OrderProcessor depends on an InventoryManager, and the InventoryManager is set as a property of the OrderProcessor, it's possible to introduce a proxy to ensure that the InventoryManager instance can be changed without invalidating the OrderProcessor reference. This mechanism is threadsafe, providing powerful "hot swap" capabilities. Full-blown AOP isn't the only way to do this, but if a proxy is to be introduced at all, enabling the full power of Spring AOP makes sense.
  • "Dynamic object" support: As with hot swapping, the use of an AOP proxy can enable "dynamic" objects such as objects sourced from scripts in a language such as Groovy or Beanshell to support reload (changing the underlying instance) and (using introduction) implement additional interfaces allowing state to be queried and manipulated (last refresh, forcible refresh, and so on).
  • Security: Acegi Security for Spring is an associated framework that uses Spring AOP to deliver a sophisticated declarative security model.

There's also a compelling value proposition in using AOP in application code, and Spring provides a flexible, extensible framework for doing so. AOP is often used in applications to handle aspects such as:

  • Auditing: Applying a consistent auditing policy — for example, to updates on persistent objects.
  • Exception handling: Applying a consistent exception handling policy, such as emailing an administrator in the event of a particular set of exceptions being thrown by a business object.
  • Caching: An aspect can maintain a cache transparent to proxied objects and their callers, providing a simple means of optimization.
  • Retrying: Attempting transparent recovery: for example, in the event of a failed remote invocation.

AOP seems set to be the future of middleware, with services (pre-built or application-specific) flexibly applied to POJOs. Unlike the monolithic EJB container, which provides a fixed set of services, AOP offers a much more modular approach. It offers the potential to combine best-of-breed services: for example, transaction management from one vendor, and security from another.

While the full AOP story still has to be played out, Spring makes a substantial part of this vision a reality today, with solid, proven technology that is in no way experimental.