The Spring Framework is an open source application framework that aims to make J2EE development easier. In this chapter we'll look at the motivation for Spring, its goals, and how Spring can help you develop high-quality applications quickly.
Spring is an application framework. 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.
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. In the many code reviews we've done as consultants, time and time again we see 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 tradeoff 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, we 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.
Some parts of J2EE can properly be termed frameworks themselves. Among them, EJB amounts to a framework because it provides a structure for application code, and defines a consistent way of accessing services from the application server. However, the EJB framework is cumbersome to use and restrictive. The work involved in implementing an EJB is excessive, given that the architects of J2EE expected that all business logic in J2EE applications would be implemented in EJBs. Developers must cope with three to four Java classes for each EJB; two verbose deployment descriptors for each EJB JAR file; and excessive amounts of code for client access to EJBs and EJB access to their environment. The EJB component model, up to and including EJB 2.1, fails to deliver on many of its goals, and fails to deliver a workable structure for business logic in J2EE applications. The EJB Expert Group has finally realized this and is attempting an overhaul of the EJB model in EJB 3.0, but we need a solution, right now, and Spring already demonstrates a far superior one in most cases.
Not merely EJB, but the majority of frameworks in the early years of J2EE, proved to have problems of their own. For example, Apache Avalon offered powerful configuration management and other services, but never achieved widespread adoption, partly because of the learning curve it required, and because application code needed to be aware of Avalon APIs.
A framework can only be as good as the programming model it provides. If a framework imposes too many requirements on code using it, it creates lock-in and — even more important — constrains developers in ways that may not be appropriate. The application developer, rather than framework designer, often has a better understanding of how code should be written.
Yet a framework should provide guidance with respect to good practice: It should make the right thing easy to do. Getting the right mixture of constraint and freedom is the key challenge of framework design, which is as much art as science.
Given this history, the emergence of a number of lightweight frameworks was inevitable. These aim to provide many of the services of "out of the box" J2EE in a simpler, more manageable manner. They aim to do their best to make the framework itself invisible, while encouraging good practice. Above all, they aim to enable developers to work primarily with POJOs, rather than special objects such as EJBs.
As the name implies, lightweight frameworks not only aim to reduce complexity in application code, but avoid unnecessary complexity in their own functioning. So a lightweight framework won't have a high startup time, won't involve huge binary dependencies, will run in any environment, and won't place obstacles in the way of testing.
While "old J2EE" was characterized by high complexity and a welter of questionable "design patterns" to give it intellectual respectability, lightweight J2EE is about trying to find the "simplest thing that can possibly work": wise advice from the XP methodology, regardless of whether you embrace XP practices overall.
While all the lightweight frameworks grew out of J2EE experience, it's important to note that none of them is J2EE-specific. A lightweight container can be used in a variety of environments: even in applets.
For example, the Spring Rich Client project demonstrates the value of the Spring model outside the server environment, in rich client applications.
Spring is both the most popular and most ambitious of the lightweight frameworks. It is the only one to address all architectural tiers of a typical J2EE application, and the only one to offer a comprehensive range of services, as well as a lightweight container. We'll look at Spring's modules in more detail later, but the following are the key Spring modules:
Inversion of Control container: The core "container" Spring provides, enabling sophisticated configuration management for POJOs. The Spring IoC container can manage fine or coarse- grained POJOs (object granularity is a matter for developers, not the framework), and work with other parts of Spring to offer services as well as configuration management. We'll explain IoC and Dependency Injection later in this chapter.
Aspect-Oriented Programming (AOP) framework: AOP enables behavior that would otherwise be scattered through different methods to be modularized in a single place. Spring uses AOP under the hood to deliver important out-of-the-box services such as declarative transaction management. Spring AOP can also be used to implement custom code that would otherwise be scattered between application classes.
Data access abstraction: Spring encourages a consistent architectural approach to data access, and provides a unique and powerful abstraction to implement it. Spring provides a rich hierarchy of data access exceptions, independent of any particular persistence product. It also provides a range of helper services for leading persistence APIs, enabling developers to write persistence framework–agnostic data access interfaces and implement them with the tool of their choice.
JDBC simplification: Spring provides an abstraction layer over JDBC that is significantly simpler and less error-prone to use than JDBC when you need to use SQL-based access to relational databases.
Transaction management: Spring provides a transaction abstraction that can sit over JTA "global" transactions (managed by an application server) or "local" transactions using the JDBC, Hibernate, JDO, or another data access API. This abstraction provides a consistent programming model in a wide range of environments and is the basis for Spring's declarative and programmatic transaction management.
MVC web framework: Spring provides a request-based MVC web framework. Its use of shared instances of multithreaded "controllers" is similar to the approach of Struts, but Spring's web framework is more flexible, and integrates seamlessly with the Spring IoC container. All other Spring features can also be used with other web frameworks such as Struts or JSF.
Simplification for working with JNDI, JTA, and other J2EE APIs: Spring can help remove the need for much of the verbose, boilerplate code that "doesn't do anything." With Spring, you can continue to use JNDI or EJB, if you want, but you'll never need to write another JNDI lookup. Instead, simple configuration can result in Spring performing the lookup on your behalf, guaranteeing that resources such as JNDI contexts are closed even in the event of an exception. The dividend is that you get to focus on writing code that you need to write because it relates to your business domain.
Lightweight remoting: Spring provides support for POJO-based remoting over a range of protocols, including RMI, IIOP, and Hessian, Burlap, and other web services protocols.
JMS support: Spring provides support for sending and receiving JMS messages in a much simpler way than provided through standard J2EE.
JMX support: Spring supports JMX management of application objects it configures.
Support for a comprehensive testing strategy for application developers: Spring not only helps to facilitate good design, allowing effective unit testing, but provides a comprehensive solution for integration testing outside an application server.