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

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

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:

  • 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. See Chapter 2 for a discussion of the issues around this advanced form of Dependency Injection.

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:

  • Writing boilerplate code to apply the services to every method that requires them: Like all cut-and-paste approaches, this is unmaintainable; if we need to modify how a service is delivered, we need to change multiple blocks of code, and OOP alone can't help us modularize that code. Furthermore, each additional concern will result in its own boilerplate code, threatening to obscure the business purpose of each method. We 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 we must build in support for the pattern by making our 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 we need a custom service, or don't like the way in which the EJB specification approaches the particular concern. For example, we can't use EJB services if we 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 our code — we 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 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:

  • 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 our 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.

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:

  • Whether it's desirable to depend on a particular API depends more on the nature of that API than its provenance. For example, if depending on a "standard" API results in code that is hard to unit test, you are better off with an abstraction over that API. A good example of this is JavaMail. JavaMail is a particularly difficult API to test against because of its use of static methods and final classes.

  • You may well end up building such an abstraction anyway; there's a real value in having it off the shelf, in a widely used product. The reality is that Spring competes with in-house frameworks, rather than with J2EE itself. Most large applications will end up building helper objects and a level of abstraction over cumbersome APIs such as JNDI — as we noted early in this chapter, using J2EE out of the box has generally not proved to be a workable option. Spring provides a well-thought-through abstraction that is widely applicable and requires you to write no custom code.

  • Dependency on Spring is limited to a set of interfaces; the dependencies are simple and explicit, avoiding implicit dependencies, which can be problematic. For example, if you write code that performs programmatic transaction management using JTA, you have a whole range of implicit dependencies. You need to obtain the JTA UserTransaction object using JNDI; you thus need both a JNDI and JTA environment. This may not be available in all environments, or at test time. In some important cases, such as declarative transaction management, no dependencies on Spring APIs are required to use Spring's abstraction.

  • Spring's abstraction interfaces work in a wide range of environments. If you write code that uses JTA, you are tied to an environment providing JNDI and JTA. For example, if you try to run that code in a web container such as Tomcat, you would otherwise need to plug in a third- party JTA implementation. Spring's transaction infrastructure can help you avoid this need in the majority of cases, where you are working with a single database.

  • Spring's abstraction can decouple you from heavy dependence on third-party APIs that may be subject to change, or in case you switch product. For example, if you aren't 100 percent sure whether you want to use Hibernate or TopLink persistence, you can use Spring's data access abstraction to make any migration a relatively straightforward process. Alternatively, if you need to migrate from Hibernate 2 to Hibernate 3, you'll find that easier if you use Spring.

  • Spring APIs are written for use by application developers, rather than merely for usage behind the scenes. This relates to one of the key points made earlier in this chapter about the value proposition of frameworks in general. Both JTA and JDBC are good counter-examples. JTA was essentially intended to work behind the scenes to enable EJB declarative transaction management. Thus, exception handling using JTA is particularly cumbersome, with multiple exceptions having no common base class and needing individual catch blocks. JDBC is a relatively successful API at providing a consistent view of any relational database, but is extremely verbose and problematic to use directly in application code because of the complex error-handling required, and because of the need to write non-portable code to try to pinpoint the cause of a particular failure and work with advanced scenarios such as working with BLOBs and calling stored procedures.

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:

  • Infrastructure exceptions are not usually recoverable. While it's possible to catch unchecked exceptions when one is recoverable, there is no benefit in being forced to catch or throw exceptions in the vast majority of cases where no recovery is possible.

  • Checked exceptions lessen the value of an exception hierarchy. If Spring's DataAccessException were checked, for example, it would be necessary to write a catch (DataAccessException) block every time a subclass, such as IntegrityViolationException, was caught, or for other, unrecoverable DataAccessExceptions to be handled in application code up the call stack. This cuts against the benefit of compiler enforcement, as the only useful catch block, for the subclass that can actually be handled, is not enforced by the compiler.

  • Try/catch blocks that don't add value are verbose and obfuscate code. It is not lazy to want to avoid pointless code; it obfuscates intention and will pose higher maintenance costs forever. Avoiding overuse of checked exceptions is consistent with the overall Spring goal of reducing the amount of code that doesn't do anything in typical applications.

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:

  • JDBC requires Connections, Statements, and ResultSets to be obtained and released, even in the event of errors.

  • Hibernate requires Sessions to be obtained and released, taking into account any current transaction; JDO and TopLink have similar requirements, for PersistenceManagers and Sessions, respectively.

  • Correct usage of JNDI requires Contexts to be acquired and closed.

  • JTA has both its own requirements and a requirement to use JNDI.

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:

  • Hibernate: The leading open source O/R mapping tool. Hibernate was the first O/R mapping tool for which Spring offered integration. Spring's Hibernate integration makes Hibernate significantly easier to use, through the HibernateTemplate we've briefly discussed and through integration with Spring's transaction management.

  • JDO implementations: Spring provides support for JDO 1.0 and JDO 2.0 standards. Several JDO vendors also ship their own Spring integration, implementing Spring's JdoDialect interface, giving application developers access to common capabilities that go beyond the JDO specification without locking into a particular JDO vendor.

  • TopLink: TopLink is the oldest O/R mapping tool on the market, dating back to the mid-1990s. TopLink is now an Oracle product, and Oracle has written a Spring integration that enables Spring users to work as seamlessly with TopLink as with Hibernate or a JDO implementation.

  • Apache OJB: An O/R mapping product from Apache.

  • iBATIS: iBATIS SQL Maps is not, strictly speaking, an O/R mapping product. However, it does offer a convenient way of defining SQL statements in a declarative fashion, mapping objects to statement parameters and result sets to objects. In contrast to full-blown O/R mapping solutions, though, SQL Maps does not aim to provide an object query language or automatic change detection.

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:

  • Struts: Still the dominant MVC web framework (although in decline). Many Spring users use Struts with a Spring middle tier. Integration is fairly close, and it is even possible to configure Struts Actions using Spring Dependency Injection, giving them instant access to middle-tier objects without any Java coding.

  • WebWork: Integration with WebWork is particularly close because of WebWork's flexible design and the strong interest in the WebWork community in using WebWork along with a Spring middle tier. It is possible to configure WebWork Actions using Spring Dependency Injection.

  • Spring MVC: Spring's own MVC web framework, which of course integrates particularly well with a Spring middle tier.

  • Tapestry: A component-oriented framework from Apache's Jakarta group, Tapestry integrates well with Spring because declarative page metadata makes it easy for Tapestry pages to access Spring-provided services without any code-level coupling to Spring.

  • JSF: Spring integrates very well with JSF, given that JSF has the concept of "named beans" and does not aim to implement middle-tier services itself.

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.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net