Professional Java Development with the Spring Framework

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, and specifically the Dependency Injection flavor of Inversion of Control. We'll discuss this concept in detail in Chapter 2, "The Bean Factory and Application Context," but it's important to begin here with an overview. 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.

Note 

Inversion of Control is often abbreviated as IoC in the remainder of this book.

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.

Important 

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 constructorsthat 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:

We find that 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:

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

Enough theory: Let's look at a simple example of an object being configured using Dependency Injection.

We 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.

Let's assume that our 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 we 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, we 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, we haven't shown any business methods 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, as we'll see in the next section.) 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 call- backs, 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.

Important 

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; we also need to provide declarative services to objects. It's a great start to be able to configure our POJOs — even with a rich network of collaborators — without constraining their design; it's equally important tobe 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, we are talking about a particular use of AOP here, rather than the be all and end all of AOP.)

AOP provides a different way of thinking about code structure, compared to OOP or procedural programming. Whereas in OOP we model real-world objects or concepts, such as bank accounts, as objects, and organize those objects in hierarchies, AOP enables us to think about concerns or aspects in our 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 we end up with a choice of tradeoffs:

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

AOP enables us 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). Don't worry if AOP terms such as "pointcuts" and"introduction" are unfamiliar now; we'll provide a brief introduction to AOP in Chapter 4, which covers Spring's AOP framework.

Here, we're more interested 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:

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:

See Chapter 4, "Spring and AOP," for an introduction to AOP concepts and the Spring AOP framework.

Important 

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.

Consistent Abstraction

If we return to the core Spring mission of providing declarative service to POJOs, we see that it's not sufficient to have an AOP framework. From a middleware perspective, an AOP framework is a way of delivering services; it's important to have services to deliver — for example, to back a transaction management aspect. And of course not all services can be delivered declaratively; for example, there's no way to avoid working with an API for data access.

Important 

The third side of the Spring triangle, after IoC and AOP, is a consistent service abstraction

Motivation

At first glance the idea of Spring providing a consistent "service abstraction" may seem puzzling. Why is it better to depend on Spring APIs than, say, standard J2EE APIs such as JNDI, or APIs of popular, solid open source products such as Hibernate?

The Spring abstraction approach is compelling for many reasons:

In keeping with Spring's philosophy of not reinventing the wheel, Spring does not provide its own abstraction over services unless there are proven difficulties, such as those we've just seen, relating to use of that API.

Of course it's important to ensure that the abstraction does not sacrifice power. In many cases, Spring allows you to leverage the full power of the underlying service to express operations, even using the native API. However, Spring will take care of plumbing and resource handling for you.

Exceptions

A consistent exception hierarchy is essential to the provision of a workable service abstraction. Spring provides such an exception hierarchy in several cases, and this is one of Spring's unique features. The most important concerns data access. Spring's org.springframework.dao.DataAccessException and its subclasses provide a rich exception model for handling data access exceptions. Again, the emphasis is on the application programming model; unlike the case of SQLException and many other data access APIs, Spring's exception hierarchy is designed to allow developers to write the minimum, cleanest code to handle errors.

DataAccessException and other Spring infrastructure exceptions are unchecked. One of the Spring principles is that infrastructure exceptions should normally be unchecked. The reasoning behind this is that:

Using checked exceptions for infrastructural failures sounds good in theory, but practice shows differently. For example, if you analyze real applications using JDBC or entity beans (both of which APIs use checked exceptions heavily), you will find a majority of catch blocks that merely wrap the exception(often losing the stack trace), rather than adding any value. Thus not only the catch block is often redundant, there are also often many redundant exception classes.

To confirm Spring's choice of unchecked infrastructure exceptions, compare the practice of leading persistence frameworks: JDO and TopLink have always used unchecked exceptions; Hibernate 3 will switch from checked to unchecked exceptions.

Of course it's essential that a framework throwing unchecked exceptions must document those exceptions. Spring's well-defined, well-documented hierarchies are invaluable here; for example, any code using Spring data access functionality may throw DataAccessException, but no unexpected unchecked exceptions (unless there is a lower-level problem such as OutOfMemoryError, which could still occur if Spring itself used checked exceptions).

Important 

As with Dependency Injection, the notion of a simple, portable service abstraction — accompanied with a rich exception model — is conceptually so simple (although not simple to deliver) that it's surprising no one had done it before.

Resource Management

Spring's services abstraction provides much of its value through the way in which it handles resource management. Nearly any low-level API, whether for data access, resource lookup, or service access, demands care in acquiring and releasing resources, placing an onus on the application developer and leaving the potential for common errors.

The nature of resource management differs depending on API and the problems it brings:

Spring applies a consistent approach, whatever the API. While in some cases JCA can solve some of the problems (such as binding to the current thread), it is complex to configure, requires a full-blown application server, and is not available for all resources. Spring provides a much more lightweight approach, equally at home inside or outside an application server.

Spring handles resource management programmatically, using callback interfaces, or declaratively, using its AOP framework.

Spring calls the classes using a callback approach based on templates. This is another form of Inversion of Control; the application developer can work with native API constructs such as JDBC Connections and Statements without needing to handle API-specific exceptions (which will automatically be translated into Spring's exception hierarchy) or close resources (which will automatically be released by the framework).

Spring provides templates and other helper classes for all supported APIs, including JDBC, Hibernate, JNDI, and JTA.

Compare the following code using Spring's JDBC abstraction to raw JDBC code, which would require a try/catch/finally block to close resources correctly:

Collection requests = jdbc.query("SELECT NAME, DATE, ... + FROM REQUEST WHERE SOMETHING = 1", new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Request r = new Request(); r.setName(rs.getString("NAME")); r.setDate(rs.getDate("DATE")); return r; } });

If you've worked with raw JDBC in a production environment, you will know that the correct raw JDBC alternative is not a pretty sight. Correct resource handling in the event of failures is particularly problematic, requiring a nested try/catch block in the finally block of an overall try/catch/finally block to ensure that the connection and other resources are always closed, and there is no potential for connection leaks, which are a severe and common problem in typical JDBC code.

When using Spring JDBC, the developer doesn't need to handle SQLException, although she can choose to catch Spring's more informative DataAccessException or subclasses. Even in the event of an SQLException being thrown, the Connection (which is obtained by the framework) and all other resources will still be closed. Nearly every line of code here does something, whereas with raw JDBC most code would be concerned with plumbing, such as correct release of resources.

Many common operations can be expressed without the need for callbacks, like SQL aggregate functions or the following query using Spring's HibernateTemplate:

List l = hibernateTemplate.find("from User u where u.name = ?", new Object[] { name });

Spring will automatically obtain a Hibernate Session, taking into account any active transaction, in which case a Session will be bound to the current thread.

This approach is used consistently within the framework for multiple APIs, such as JDBC, JTA, JNDI, Hibernate, JDO, and TopLink. In all cases, exception translation, as well as resource access, is handled by the framework.

Techniques

As important as the technologies are the techniques that they enable. As we've noted, Spring is partly a manifestation of the movement toward agile processes, and it solves many of the problems that agile practitioners otherwise encounter when working with J2EE.

For example, Test Driven Development (TDD) is one of the key lessons of XP (Extreme Programming) — although of course, the value of rigorous unit testing has long been recognized (if too rarely practiced).

Unit testing is key to success, and a framework must facilitate it. Spring does — as do other lightweight containers such as PicoContainer and HiveMind; recognition of the central importance of unit testing is not unique to Spring, nor is Spring the only product to rise to the challenge.

Unfortunately, J2EE out of the box is not particularly friendly to unit testing. Far too high a proportion of code in a traditional J2EE application depends on the application server, meaning that it can be tested only when deployed to a container, or by stubbing a container at test time.

Both of these approaches have proven problematic. The simple fact is that in-container testing is too slow and too much of an obstacle to productivity to apply successfully in large projects. While tools such as Cactus exist to support it, and some IDEs provide test time "containers," our experience is that it is rare to see an example of a well unit-tested "traditional" J2EE architecture.

Spring's approach — non-invasive configuration management, the provision of services to POJOs, and consistent abstractions over hard-to-stub APIs — makes unit testing outside a container (such as in simple JUnit tests) easy. So easy that TDD is a joy to practice, and an agile process is greatly facilitated.

Important 

Interestingly, while Spring plays so well with agile approaches, it can also work very well with supposedly "heavyweight" methodologies. We've seen a Spring- based architecture work far better with the Unified Software Development Process than a traditional J2EE architecture because it demands fewer compromises for implementation strategy. (Traditional J2EE approaches often have a problematic gulf between Analysis and Design Models because of the workarounds of "J2EE design patterns.")

Note 

Spring even provides support for integration testing using a Spring context, but out of a container, in the org.springframework.test package. This is not an alternative to unit tests (which should not normally require Spring at all), but can be very useful as the next phase of testing. For example, the test superclasses in this package provide the ability to set up a transaction for each test method and automatically tear it down, doing away with the necessity for database setup and teardown scripts that might otherwise slow things down significantly.

Relationship to Other Frameworks

As we've noted, Spring does not reinvent the wheel. Spring aims to provide the glue that enables you to build a coherent and manageable application architecture out of disparate components.

Let's summarize how Spring relates to some of the many products it integrates with.

Persistence Frameworks

Spring does not provide its own O/R mapping framework. It provides an abstraction over JDBC, but this is a less painful way of doing exactly the same thing as might otherwise have been done with JDBC.

Spring provides a consistent architectural model, but allows you to choose the O/R mapping framework of your choice (or an SQL-based approach where appropriate). For the many applications that benefit from using O/R mapping, you should integrate an O/R mapping framework with Spring. Spring integrates well with all leading O/R mapping frameworks. Supported choices include:

All these integrations are consistent in that Spring facilitates the use of DAO interfaces, and all operations throw informative subclasses of Spring's DataAccessException. Spring provides helpers such as templates for all these APIs, enabling a consistent programming style. Spring's comprehensive architectural template means that almost any persistence framework can be integrated within this consistent approach. Integration efforts from several JDO vendors — the fact that the Spring/TopLink integration was developed by the TopLink team at Oracle, and that the popular Cayenne open source O/R mapping project itself developed Spring integration for Cayenne — shows that Spring's data access abstraction is becoming something of a de facto standard for consistent approach to persistence in Java/J2EE applications.

Spring's own JDBC framework is suitable when you want SQL-based access to relational data. This is not an alternative to O/R mapping, but it's necessary to implement at least some scenarios in most applications using a relational database. (Also, O/R mapping is not universally applicable, despite the claims of its more enthusiastic advocates.)

Importantly, Spring allows you to mix and match data access strategies — for example Hibernate code and JDBC code sharing the same connection and transaction. This is an important bonus for complex applications, which typically can't perform all persistence operations using a single persistence framework.

Web Frameworks

Again, the fundamental philosophy is to enable users to choose the web framework of their choice, while enjoying the full benefit of a Spring middle tier. Popular choices include:

Spring's approach to web frameworks differs from that to persistence, in that Spring provides its own fully featured web framework. Of course, you are not forced to use this if you wish to use a Spring middle tier. While Spring integrates well with other web frameworks, there are a few integration advantages available only with Spring's own MVC framework, such as the ability to use some advanced features of Spring Dependency Injection, or to apply AOP advice to web controllers. Nevertheless, as Spring integrates well with other web frameworks, you should choose Spring's own MVC framework on its merits, rather than because there's any element of compulsion. Spring MVC is an appealing alternative to Struts and other request-driven frameworks as it is highly flexible, helps to ensure that application code in the web tier is easily testable, and works particularly well with a wide variety of view technologies besides JSP. In Chapter 12 we discuss Spring MVC in detail.

AOP Frameworks

Spring provides a proxy-based AOP framework, which is well suited for solving most problems in J2EE applications.

However, sometimes you need capabilities that a proxy-based framework cannot provide, such as the ability to advise objects created using the new operator and not managed by any factory; or the ability to advise fields, as well as methods.

To support such requirements, Spring integrates well with AspectJ and ApectWerkz, the two leading class weaving–based AOP frameworks. It's possible to combine Spring Dependency Injection with such AOP frameworks — for example, configuring AspectJ aspects using the full power of the Spring IoC container as if they were ordinary classes.

Important 

As of early 2005, the AspectJ and AspectWerkz projects are merging into AspectJ 5.0, which looks set to be the definitive full-blown AOP framework. The Spring project is working closely with the AspectJ project to ensure the best possible integration, which should have significant benefits for both communities.

Spring does not attempt to replicate the power of a full-blown AOP solution such as AspectJ; this would produce no benefits to Spring users, who are instead free to mix Spring AOP with AspectJ as necessary to implement sophisticated AOP solutions.

Other Frameworks

Spring integrates with other frameworks including the Quartz scheduler, Jasper Reports, and Velocity and FreeMarker template engines.

Again, the goal is to provide a consistent backbone for application architecture.

All such integrations are modules, distinct from the Spring core. While some ship with the main Spring distribution, some are external modules. Spring's open architecture has also resulted in numerous other projects (such as the Cayenne O/R mapping framework) providing their own Spring integration, or providing Spring integration with third-party products.

Категории