CSDN博客

img nzh_csdn

Introducing the Spring Framework

发表于2004/10/21 20:50:00  1254人阅读

Introducing the Spring Framework 
October 2003

Discuss this Article


You may have heard the buzz this summer around the Spring Framework. In this article, I'll try to explain what Spring sets out to achieve, and how I believe it can help you to develop J2EE applications.


Yet another framework?

You may be thinking "not another framework." Why should you bother to read this article, or download the Spring Framework, when there are already many open source (and proprietary) J2EE frameworks?

I believe that Spring is unique, for several reasons:

  • It addresses areas that many other popular frameworks don't. Spring focuses around providing a way to manage your business objects.

  • Spring is both comprehensive and modular. Spring has a layered architecture, meaning that you can choose to use just about any part of it in isolation, yet its architecture is internally consistent. So you get maximum value from your learning curve. You might choose to use Spring only to simplify use of JDBC, for example, or you might choose to use Spring to manage all your business objects.

  • It's designed from the ground up to help you write code that's easy to test. Spring is an ideal framework for test driven projects.

Spring is not necessarily one more framework dependency for your project. Spring is potentially a one-stop shop, addressing most infrastructure concerns of typical applications. It also goes places other frameworks don't.

Although it has been an open source project only since February 2003, Spring has a long heritage. The open source project started from infrastructure code published with my book, Expert One-on-One J2EE Design and Development, in late 2002. Expert One-on-One J2EE also laid out the basic architectural thinking behind Spring. However, the architectural concepts go back to early 2000, and reflect my experience in developing infrastructure for a series of successful commercial projects.

Since January 2003, Spring has been hosted on SourceForge. There are now ten developers, of whom six are highly active.


Architectural benefits of Spring

Before we get down to specifics, let's look at some of the benefits Spring can bring to a project:

  • Spring can effectively organize your middle tier objects, whether or not you choose to use EJB. Spring takes care of plumbing that would be left up to you if you use only Struts or other frameworks geared to particular J2EE APIs.

  • Spring can eliminate the proliferation of Singletons seen on many projects. In my experience, this is a major problem, reducing testability and object orientation.

  • Spring can eliminate the need to use a variety of custom properties file formats, by handling configuration in a consistent way throughout applications and projects. Ever wondered what magic property keys or system properties a particular class looks for, and had to read the Javadoc or even source code? With Spring you simply look at the class's JavaBean properties. The use of Inversion of Control (discussed below) helps achieve this simplification.

  • Spring can facilitate good programming practice by reducing the cost of programming to interfaces, rather than classes, almost to zero.

  • Spring is designed so that applications built with it depend on as few of its APIs as possible. Most business objects in Spring applications have no dependency on Spring.

  • Applications built using Spring are very easy to unit test.

  • Spring can make the use of EJB an implementation choice, rather than the determinant of application architecture. You can choose to implement business interfaces as POJOs or local EJBs without affecting calling code.

  • Spring helps you solve many problems without using EJB. Spring can provide an alternative to EJB that's appropriate for many web applications. For example, Spring can use AOP to deliver declarative transaction management without using an EJB container; even without a JTA implementation, if you only need to work with a single database.

  • Spring provides a consistent framework for data access, whether using JDBC or an O/R mapping product such as Hibernate.

Spring really can enable you to implement the simplest possible solution to your problems. And that's worth a lot.


What does Spring do?

Spring provides a lot of functionality, so I'll quickly review each major area in turn.


Mission statement

Firstly, let's be clear on Spring's scope. Although Spring covers a lot of ground, we do have clear vision as to what it should and shouldn't address.

Spring's main aim is to make J2EE easier to use and promote good programming practice.

Spring does not reinvent the wheel. Thus you'll find no logging packages in Spring, no connection pools, no distributed transaction coordinator. All these things are provided by open source projects (such as Commons Logging, which we use for all our log output, or Commons DBCP), or by your application server. For the same reason, we don't provide an O/R mapping layer. There are good solutions to this problem such as Hibernate and JDO.

Spring does aim to make existing technologies easier to use. For example, although we are not in the business of low-level transaction coordination, we do provide an abstraction layer over JTA or any other transaction strategy.

Spring doesn't directly compete with other open source projects unless we feel we can provide something new. For example, like many developers, we have never been happy with Struts, and feel that there's room for improvement in MVC web frameworks. In some areas, such as its lightweight IoC container and AOP framework, Spring does have direct competition, but these are areas in which no solution has become popular. (Spring was a pioneer in these areas.)

Spring also benefits from internal consistency. All the developers are singing from the same hymn sheet, the fundamental ideas remaining faithful to those of Expert One-on-One J2EE Design and Development. And we've been able to use some central concepts, such as Inversion of Control, across multiple areas.

Spring is portable between application servers. Of course ensuring portability is always a challenge, but we avoid anything platform-specific or non-standard, and support users on WebLogic, Tomcat, Resin, JBoss, WebSphere and other application servers.


Inversion of control container

The core of Spring's design is the org.springframework.beans package, designed for working with JavaBeans. This package typically isn't used directly by users, but serves as the underpinning of much of the other functionality.

The next higher layer of abstraction is the "Bean Factory." A Spring bean factory is a generic factory that enables objects to be retrieved by name, and which can manage relationships between objects.

Bean factories support two modes of objects:

  • "Singleton": in this case, there's one shared instance of the object with a particular name, which will be retrieved on lookup. This is the default, and most often used, mode. It's ideal for stateless service objects.

  • "Prototype": in this case, each retrieval will result in the creation of an independent object. For example, this could be used to allow each user to have their own object.

As org.springframework.beans.factory.BeanFactory is a simple interface, it can be implemented for a range of underlying storage methods. You could easily implement your own, although few users find they need to. The most commonly used BeanFactory definitions are:

  • The XmlBeanFactory. This parses a simple, intuitive XML structure defining the classes and properties of named objects. We provide a DTD to make authoring easier.

  • ListableBeanFactoryImpl: This provides the ability to parse bean definitions in properties files, and create BeanFactories programmatically.

Each bean definition can be a POJO (defined by class name and JavaBean initialisation properties), or a FactoryBean. The FactoryBean interface adds a level of indirection. Typically this is used to create proxied objects using AOP or other approaches: for example, proxies that add declarative transaction management. (This is similar to EJB interception in concept, but works out much simpler in practice.)

BeanFactories can optionally participate in a hierarchy, "inheriting" definitions from their ancestors. This enables the sharing of common configuration across a whole application, while individual resources such as controller servlets also have their own independent set of objects.

This motivation for the use of JavaBeans is described in Chapter 4 of Expert One-on-One J2EE Design and Development, which is available on the ServerSide as a free PDF (/articles/article.tss?l=RodJohnsonInterview).

Through its BeanFactory concept, Spring is an Inversion of Control container. (I don't much like the term container, as it conjures up visions of heavyweight containers such as EJB containers. A Spring BeanFactory is a container that can be created in a single line of code, and requires no special deployment steps.)

The concept behind Inversion of Control is often expressed in the Hollywood Principle: "Don't call me, I'll call you." IoC moves the responsibility for making things happen into the framework, and away from application code. Where configuration is concerned this means that while in traditional container architectures such as EJB, a component might call the container to say "where's object X, which I need to do my work", with IoC the container figures out that the component needs an X object, and provides it to it at runtime. The container does this figuring out based on method signatures (such as JavaBean properties) and, possibly, configuration data such as XML.

IoC has several important benefits. For example:

  • Because components don't need to look up collaborators at runtime, they're much simpler to write and maintain. In Spring's version of IoC, components express their dependency on other components via exposing JavaBean setter methods. The EJB equivalent would be a JNDI lookup, which requires the developer to write code.

  • For the same reasons, application code is much easier to test. JavaBean properties are simple, core Java and easy to test: just write a self-contained JUnit test method that creates the object and sets the relevant properties.

  • A good IoC implementation preserves strong typing. If you need to use a generic factory to look up collaborators, you have to cast the results to the desired type. This isn't a major problem, but it is inelegant. With IoC you express strongly typed dependencies in your code and the framework is responsible for type casts. This means that type mismatches will be raised as errors when the framework configures the application; you don't have to worry about class cast exceptions in your code.

  • Most business objects don't depend on IoC container APIs. This makes it easy to use legacy code, and easy to use objects either inside or outside the IoC container. For example, Spring users often configure the Jakarta Commons DBCP DataSource as a Spring bean: there's no need to write any custom code to do this. We say that an IoC container isn't invasive: using it won't invade your code with dependency on its APIs. Any JavaBean can become a component in a Spring bean factory.

This last point deserves emphasis. IoC is unlike traditional container architectures, such as EJB, in this minimization of dependency of application code on container. This means that your business objects can potentially be run in different IoC frameworks - or outside any framework - without code changes.

In my experience and that of Spring users, it's hard to overemphasize the benefits that IoC brings to application code.

IoC is not a new concept, although it's only just made prime time in the J2EE community. There are alternative IoC containers: notably, Apache Avalon, PicoContainer and HiveMind. Avalon has never become particularly popular, although it is powerful and has a long history. Avalon is fairly heavyweight and complex, and seems more invasive than newer IoC solutions. PicoContainer is lightweight and emphasizes the expression of dependencies through constructors rather than JavaBean properties. Unlike Spring, its design allows the definition of only one object of each type (possibly a limitation resulting from its rejection of any metadata outside Java code). For a comparison between Spring and PicoContainer and other IoC frameworks, see the article "The Spring Framework - A Lightweight Container" on the Spring website at http://www.springframework.org/docs/lightweight_container.html. This page includes a link to the PicoContainer website.

Spring BeanFactories are very lightweight. Users have successfully used them inside applets, as well as standalone Swing applications. (They also work fine within an EJB container.) There are no special deployment steps and no detectable startup time. This ability to instantiate a container almost instantly in any tier of an application can be very valuable.

The Spring BeanFactory concept is used throughout Spring, and is a key reason that Spring is so internally consistent. Spring is also unique among IoC containers in that it uses IoC as a basic concept throughout a full-featured framework.

Most importantly for application developers, one or more BeanFactories provide a well-defined layer of business objects. This is analogous to, but much simpler than, a layer of local session beans. Unlike EJBs, the objects in this layer can be interrelated, and their relationships managed by the owning factory. Having a well-defined layer of business objects is very important to a successful architecture.

A Spring ApplicationContext is a subinterface of BeanFactory, which provides support for:

  • Message lookup, supporting internationalization

  • An eventing mechanism, allowing application objects to publish and optionally register to be notified of events

  • Portable file and resource access

XmlBeanFactory example

Spring users normally configure their applications in XML "bean definition" files. The root of a Spring XML bean definition document is a <beans> element. The <beans> element contains one or more <bean> definitions. We normally specify the class and properties of each bean definition. We must also specify the id, which will be the name that we'll use this bean with in our code.

Let's look at a simple example, which configures three application objects with relationships commonly seen in J2EE applications:

  • A J2EE DataSource

  • A DAO that uses the DataSource

  • A business object that uses the DAO in the course of its work

In the following example, we use a BasicDataSource from the Jakarta Commons DBCP project. This class (like many other existing classes) can easily be used in a Spring bean factory, as it offers JavaBean-style configuration. The close method that needs to be called on shutdown can be registered via Spring's "destroy-method" attribute, to avoid the need for BasicDataSource to implement any Spring interface.

<beans>

  <bean id="myDataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
    <property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
    <property name="url"><value>jdbc:mysql://localhost:3306/mydb</value></property>
    <property name="username"><value>root</value></property>
  </bean>

All the properties of BasicDataSource we're interested in are Strings, so we specify their values with the <value> element. Spring uses the standard JavaBean PropertyEditor mechanism to convert String representations to other types if necessary.

Now we define the DAO, which has a bean reference to the DataSource. Relationships between beans are specified using the <ref> element:

  <bean id="exampleDataAccessObject"
		class="example.ExampleDataAccessObject">
    <property name="dataSource"><ref bean="myDataSource"/></property>
  </bean>

The business object has a reference to the DAO, and an int property (exampleParam):

<bean id="exampleBusinessObject"
		class="example.ExampleBusinessObject">
    <property name="dataAccessObject"><ref bean="exampleDataAccessObject"/></property>
    <property name="exampleParam"><value>10</value></property>
  </bean>

</beans>

Relationships between objects are normally set explicitly in configuration, as in this example. We consider this to be a Good Thing. However, Spring also provides what we call "autowire" support, a la PicoContainer, where it figures out the dependencies between beans. The limitation with this - as with PicoContainer - is that if there are multiple beans of a particular type it's impossible to work out which instance a dependency of that type should be resolved to. On the positive side, unsatisfied dependencies can be caught when the factory is initialized. (Spring also offers an optional dependency check for explicit configuration, which can achieve this goal.)

We could use the autowire feature as follows in the above example, if we didn't want to code these relationships explicitly:

<bean id="exampleBusinessObject"
	class="example.ExampleBusinessObject"
	autowire="byType">

    <property name="exampleParam"><value>10</value></property>
 </bean>

With this usage, Spring will work out that the dataSource property of exampleBusinessObject should be set to the implementation of DataSource it finds in the present BeanFactory. It's an error if there is none, or more than one, bean of the required type in the present BeanFactory. We still need to set the exampleParam property, as it's not a reference.

Autowire support and dependency checking is presently in CVS and will be available in Spring 1.0 M2 (due October 20, 2003). All other features discussed in this article are in the current 1.0 M1 release.

Externalizing relationships from Java code has an enormous benefit over hard coding it, as it's possible to change the XML file without changing a line of Java code. For example, we could simply change the myDataSource bean definition to refer to a different bean class to use an alternative connection pool, or a test data source. We could use Spring's JNDI location FactoryBean to get a datasource from an application server in a single alternative XML stanza.

Now let's look at the Java code for the example business object. Note that there are no Spring dependencies in the code listing below. Unlike an EJB container, a Spring BeanFactory is not invasive: you don't normally need to code awareness of it into application objects.

public class ExampleBusinessObject implements MyBusinessObject {

	private ExampleDataAccessObject dao;
	private int exampleParam;

	public void setDataAccessObject(ExampleDataAccessObject dao) {
		this.dao = dao;
	}

	public void setExampleParam(int exampleParam) {
		this.exampleParam = exampleParam;
	}

	public void myBusinessMethod() {
		// do stuff using dao
	}
}

Note the property setters, which correspond to the XML references in the bean definition document. These are invoked by Spring before the object is used.

Such application beans do not need to depend on Spring: They don't need to implement any Spring interfaces or extend Spring classes: they just need to observe JavaBeans naming convention. Reusing one outside of a Spring application context is easy, for example in a test environment. Just instantiate it with its default constructor, and set its properties manually, via setDataSource() and setExampleParam() calls. So long as you have a no-args constructor, you're free to define other constructors taking multiple properties if you want to support programmatic construction in a single line of code.

Note that the JavaBean properties are not declared on the business interface callers will work with. They're an implementation detail. We could "plug in" different implementing classes that have different bean properties without affecting connected objects or calling code.

Of course Spring XML bean factories have many more capabilities than described here, but this should give you a feel for the basic approach. As well as simple properties, and properties for which you have a JavaBeans PropertyEditor, Spring can automatically handle lists, maps and java.util.Properties.

Bean factories and application contexts are usually associated with a scope defined by the J2EE server, such as:

  • The Servlet context. In the Spring MVC framework, an application context is defined for each web application containing common objects. Spring provides the ability to instantiate such a context through a listener or servlet without dependence on the Spring MVC framework, so it can also be used in Struts, WebWork or other web frameworks.

  • A Servlet: Each controller servlet in the Spring MVC framework has its own application context, derived from the root (application-wide) application context. It's also easy to accomplish this with Struts or another MVC framework.

  • EJB: Spring provides convenience superclasses for EJB that simplify EJB authoring and provide a BeanFactory loaded from an XML document in the EJB Jar file.

These hooks provided by the J2EE specification generally avoid the need to use a Singleton to bootstrap a bean factory.

However, it's trivial to instantiate a BeanFactory programmatically if we wish. For example, we could create the bean factory and get a reference to the business object defined above in the following three lines of code:

InputStream is = getClass().getResourceAsStream("myFile.xml");
XmlBeanFactory bf = new XmlBeanFactory(is);
MyBusinessObject mbo = (MyBusinessObject) bf.getBean("exampleBusinessObject");

This code will work outside an application server: it doesn't even depend on J2EE, as the Spring IoC container is pure Java.


JDBC abstraction and data access exception hierarchy

Data access is another area in which Spring shines.

JDBC offers fairly good abstraction from the underlying database, but is a painful API to use. Some of the problems include:

  • The need for verbose error handling to ensure that ResultSets, Statements and (most importantly) Connections are closed after use. This means that correct use of JDBC can quickly result in a lot of code. It's also a common source of errors. Connection leaks can quickly bring applications down under load.

  • The relatively uninformative SQLException. JDBC does not offer an exception hierarchy, but throws SQLException in response to all errors. Finding out what actually went wrong - for example, was the problem a deadlock or invalid SQL? - involves examining the SQLState and error code. The meaning of these values varies between databases.

Spring addresses these problems in two ways:

  • By providing APIs that move tedious and error-prone exception handling out of application code into the framework. The framework takes care of all exception handling; application code can concentrate on issuing the appropriate SQL and extracting results.

  • By providing a meaningful exception hierarchy for your application code to work with in place of SQLException. When Spring first obtains a connection from a DataSource it examines the metadata to ascertain the database. It uses this knowledge to map SQLExceptions to the correct exception in its own hierarchy descended from org.springframework.dao.DataAccessException. Thus your code can work with meaningful exceptions, and need not worry about proprietary SQLState or error codes. Spring's data access exceptions are not JDBC-specific, so your DAOs are not necessarily tied to JDBC because of the exceptions they may throw.

Spring provides two levels of JDBC API. The first, in the org.springframework.jdbc.core package, uses callbacks to move control - and hence error handling and connection acquisition and release - from application code inside the framework. This is a different type of Inversion of Control, but equally valuable to that used for configuration management.

Spring uses a similar callback approach to address several other APIs that involve special steps to acquire and cleanup resources, such as JDO (acquiring and relinquishing a PersistenceManager), transaction management (using JTA) and JNDI. Spring classes that perform such callbacks are called templates.

For example, the Spring JdbcTemplate object can be used to perform a SQL query and save the results in a list as follows:

JdbcTemplate template = new JdbcTemplate(dataSource);
final List names = new LinkedList();
template.query("SELECT USER.NAME FROM USER",
	new RowCallbackHandler() {
		public void processRow(ResultSet rs) throws SQLException {
			names.add(rs.getString(1));
		}
	});

Note that application code within the callback is free to throw SQLException: Spring will catch any exceptions and rethrow them in its own hierarchy. The application developer can choose which exceptions, if any, to catch and handle.

The JdbcTemplate provides many methods to support different scenarios including prepared statements and batch updates. The Spring JDBC abstraction has a very low performance overhead beyond standard JDBC, even in applications cases involving huge result sets.

The higher level JDBC abstraction is in the org.springframework.jdbc.object package. This is built on the core JDBC callback functionality, but provides an API in which an RDBMS operation - whether query, update or stored procedure - is modelled as a Java object. This API was partly inspired by the JDO query API, which I found intuitive and highly usable.

A query object to return User objects might look like this:

class UserQuery extends MappingSqlQuery {

	public UserQuery(DataSource datasource) {
		super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?");
		declareParameter(new SqlParameter(Types.NUMERIC));
		compile();
	}

	// Map a result set row to a Java object
	protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
		User user = new User();
		user.setId(rs.getLong("USER_ID"));
		user.setForename(rs.getString("FORENAME"));
		return user;
	}

	public User findUser(long id) {
		// Use superclass convenience method to provide strong typing
		return (User) findObject(id);
	}
}

This class can be used as follows:

User user = userQuery.findUser(25);

Such objects are often inner classes inside DAOs. They are threadsafe, unless the subclass does something unusual.

Another important class in the org.springframework.jdbc.object package is the StoredProcedure class. Spring enables a stored procedure to be proxied by a Java class with a single business method. If you like, you can define an interface that the stored procedure implements, meaning that you can free your application code from depending on the use of a stored procedure at all.

The Spring data access exception hierarchy is based on unchecked (runtime) exceptions. Having worked with Spring on several projects I'm more and more convinced that this was the right decision.

Data access exceptions not usually recoverable. For example, if we can't connect to the database, a particular business object is unlikely to be able to work around the problem. One potential exception is optimistic locking violations, but not all applications use optimistic locking. It's usually bad to be forced to write code to catch fatal exceptions that can't be sensibly handled. Letting them propagate to top-level handlers like the servlet or EJB container is usually more appropriate. All Spring data access exceptions are subclasses of DataAccessException, so if we do choose to catch all Spring data access exceptions, we can easily do so.

Note that if we do want to recover from an unchecked data access exception, we can still do so. We can write code to handle only the recoverable condition. For example, if we consider that only an optimistic locking violation is recoverable, we can write code in a Spring DAO as follows:

try {
	// do work
}
catch (OptimisticLockingFailureException ex) {
	// I'm interested in this
}

If Spring data access exceptions were checked, we'd need to write the following code. Note that we could choose to write this anyway:

try {
	// do work
}
catch (OptimisticLockingFailureException ex) {
	// I'm interested in this
}
catch (DataAccessException ex) {
	// Fatal; just rethrow it
}

One potential objection to the first example - that the compiler can't enforce handling the potentially recoverable exception - applies also to the second. Because we're forced to catch the base exception (DataAccessException), the compiler won't enforce a check for a subclass (OptimisticLockingFailureException). So the compiler would force us to write code to handle an unrecoverable problem, but provide no help in forcing us to deal with the recoverable problem.

Spring's use of unchecked data access exceptions is consistent with that of many - probably most - successful persistence frameworks. (Indeed, it was partly inspired by JDO.) JDBC is one of the few data access APIs to use checked exceptions. TopLink and JDO, for example, use unchecked exceptions exclusively. Gavin King now believes that Hibernate should also have opted for unchecked exceptions.

Spring JDBC can help you in several ways:

  • You'll never need to write a finally block again to use JDBC

  • You'll need to write much less code overall

  • You'll never need to dig through your RDBMS documentation to work out what obscure error code it returns for a bad column name. Your application won't be dependent on RDBMS-specific error handling code.

  • Whatever persistence technology use, you'll find it easy to implement the DAO pattern without business logic depending on any particular data access API.

In practice we find that all this amounts to substantial productivity gains and fewer bugs. I used to loathe writing JDBC code; now I find that I can focus on the SQL I want to execute, rather than the incidentals of JDBC resource management.

Spring's JDBC abstraction can be used standalone if desired - you are not forced to use the other parts of Spring.


O/R mapping integration

Of course often you want to use O/R mapping, rather than use relational data access. Your overall application framework must support this also. Thus Spring integrates out of the box with Hibernate 2.x and JDO. Its data access architecture allows it to integrate with any underlying data access technology. Spring and Hibernate integrate particularly well.

Why would you use Hibernate plus Spring, instead of Hibernate directly? Spring adds significant value in the following areas:

  • Session management. Spring offers efficient, easy, and safe handling of Hibernate Sessions. Related code using Hibernate generally needs to use the same Hibernate "Session" object for efficiency and proper transaction handling. Spring makes it easy to transparently create and bind a session to the current thread, using either a declarative, AOP method interceptor approach, or by using an explicit, "template" wrapper class at the Java code level. Thus Spring solves many of the usage issues that repeatedly arise on the Hibernate forums.

  • Resource management. Spring application contexts can handle the location and configuration of Hibernate SessionFactories, JDBC datasources, and other related resources. This makes these values easy to manage and change.

  • Integrated transaction management. Spring allows you to wrap your Hibernate code with either a declarative, AOP style method interceptor, or an explicit 'template' wrapper class at the Java code level. In either case, transaction semantics are handled for you, and proper transaction handling (rollback, etc.) in case of exceptions is taken care of. As discussed below, you also get the benefit of being able to use and swap various transaction managers, without your Hibernate related code being affected. As an added benefit, JDBC-related code can fully integrate transactionally with Hibernate code. This is useful for handling functionality not implemented in Hibernate.

  • Exception wrapping, as described above. Spring can wrap Hibernate exceptions, converting them from proprietary, checked exceptions, to a set of abstracted runtime exceptions. This allows you to handle most persistence exceptions, which are non-recoverable, only in the appropriate layers, without annoying boilerplate catches/throws, and exception declarations. You can still trap and handle exceptions anywhere you need to. Remember that JDBC exceptions (including DB specific dialects) are also converted to the same hierarchy, meaning that you can perform some operations with JDBC within a consistent programming model.

  • To avoid vendor lock-in. While Hibernate is powerful, flexible, open source and free, it still uses a proprietary API. Given the choice, it's usually desirable to implement major application functionality using standard or abstracted APIs, in case you need to switch to another implementation for reasons of functionality, performance, or any other concerns. Spring's abstraction of Hibernate Transactions and Exceptions, along with its IoC approach which allow you to easily swap in mapper/DAO objects implementing data-access functionality, make it easy to isolate all Hibernate-specific code in one area of your application, without sacrificing any of the power of Hibernate.

  • Ease of testing. Spring's inversion of control approach makes it easy to swap the implementations and locations of Hibernate session factories, datasources, transaction managers, and mapper object implementations (if needed). This makes it much easier to isolate and test each piece of persistence-related code in isolation.

Transaction management

Abstracting a data access API is not enough; we also need to consider transaction management. JTA is the obvious solution, but it's a cumbersome API to use directly, and as a result many J2EE developers feel that EJB CMT is the only rational option for transaction management.

Spring provides its own abstraction for transaction management. Spring uses this to deliver:

  • Programmatic transaction management via a callback template analogous to the JdbcTemplate, which is much easier to use than straight JTA

  • Declarative transaction management analogous to EJB CMT, but without the need for an EJB container

Spring's transaction abstraction is unique in that it's not tied to JTA or any other transaction management technology. Spring uses the concept of a transaction strategy that decouples application code from the underlying transaction infrastructure (such as JDBC).

Why should you care about this? Isn't JTA the best answer for all transaction management? If you're writing an application that uses only a single database, you don't need the complexity of JTA. You're not interested in XA transactions or two phase commit. You may not even need a high-end application server that provides these things. But, on the other hand, you don't want to have to rewrite your code should you ever have to work with multiple data sources.

Imagine you decide to avoid the overhead of JTA by using JDBC or Hibernate transactions directly. If you ever need to work with multiple data sources, you'll have to rip out all that transaction management code and replace it with JTA transactions. This isn't very attractive and led most writers on J2EE, including myself, to recommend using global JTA transactions exclusively. Using the Spring transaction abstraction, however, you only have to reconfigure Spring to use a JTA, rather than JDBC or Hibernate, transaction strategy and you're done. This is a configuration change, not a code change. Thus, Spring enables you to write applications that can scale down as well as up.


AOP

Recently there has been much interest in applying AOP solutions to those enterprise concerns, such as transaction management, which are addressed by EJB.

The first goal of Spring's AOP support is to provide J2EE services to POJOs. This is similar to the aims of JBoss 4. However, Spring AOP has the advantage that it's portable between application servers, so there's no risk of vendor lock in. It works in either web or EJB container, and has been used successfully in WebLogic, Tomcat, JBoss, Resin, Jetty, Orion and many other application servers and web container.

Spring AOP supports method interception. Key AOP concepts supported include:

  • Interception: Custom behaviour can be inserted before or after method invocations against any interface or class. This is similar to "around advice" in AspectJ terminology.

  • Introduction: Specifying that an advice should cause an object to implement additional interfaces. This can amount to mixin inheritance.

  • Static and dynamic pointcuts: Specifying the points in program execution at which interception should take place. Static pointcuts concern method signatures; dynamic pointcuts may also consider method arguments at the point where they are evaluated. Pointcuts are defined separately from interceptors, enabling a standard interceptor to be applied in different applications and code contexts.

Spring supports both stateful (one instance per advised object) and stateless interceptors (one instance for all advice).

Spring does not support field interception. This is a deliberate design decision. I have always felt that field interception violates encapsulation. I prefer to think of AOP as complementing, rather than conflicting with, OOP. I wouldn't be surprised if in five or ten years we have travelled a lot farther on the AOP learning curve and feel comfortable giving AOP a seat at the top table of application design. (However at that point language-based solutions such as AspectJ may be far more attractive than they are today.)

Spring implements AOP using dynamic proxies (where an interface exists) or CGLIB byte code generation at runtime (which enables proxying of classes). Both these approaches work in any application server.

Spring is the first AOP framework to implement the AOP Alliance interfaces (www.sourceforge.net/projects/aopalliance). These represent an attempt to define interfaces allowing interoperability of interceptors between AOP frameworks.

There's an ongoing and not very enlightening debate on TheServerSide and elsewhere as to whether this kind of interception is "true AOP." I don't particularly care what it's called; merely whether it can be useful in practice. I'd be equally happy to call it "declarative middleware." Think of Spring AOP as a simple, lightweight alternative to local stateless session beans, which does away with the need for a monolithic EJB container, and which enables you to build your own container with just those services you need. We don't recommend advising any and every POJO, so the analogy with local SLSBs helps to clarify the recommended granularity. (However, unlike with EJB, you're free to apply Spring AOP to finer-grained objects in rarer scenarios where it's appropriate.)

Because Spring advises objects at instance, rather than class loader, level, it is possible to use multiple instances of the same class with different advice, or use unadvised instances along with advised instances.

Perhaps the commonest use of Spring AOP is for declarative transaction management. This builds on the TransactionTemplate abstraction described above, and can deliver declarative transaction management on any POJO. Depending on the transaction strategy, the underlying mechanism can be JTA, JDBC, Hibernate or any other API offering transaction management.

Spring's declarative transaction management is similar to EJB CMT with the following differences:

  • Transaction management can be applied to any POJO. We recommend that business objects implement interfaces, but this is a matter of good programming practice, and is not enforced by the framework.

  • Programmatic rollback can be achieved within a transactional POJO through using the Spring transaction API. We provide static methods for this, using ThreadLocal variables, so you don't need to propagate a context object such as an EJBContext to ensure rollback.

  • You can define "rollback rules" declaratively. Whereas EJB will not automatically roll back a transaction on an uncaught application exception (only on unchecked exceptions and other Throwables), application developers often want a transaction to roll back on any exception. Spring transaction management allows you to specify declaratively which exceptions and subclasses should cause automatic rollback. Default behaviour is as with EJB, but you can specify automatic rollback on checked, as well as unchecked exceptions. This has the important benefit of minimizing the need for programmatic rollback, which creates a dependence on the Spring transaction API (as EJB programmatic rollback does on the EJBContext).

  • Transaction management is not tied to JTA. As explained above, Spring transaction management can work with different transaction strategies.

Of course it's also possible to use Spring AOP to implement application-specific aspects. Whether or not you choose to do this depends on your level of comfort with AOP concepts, rather than Spring's capabilities, but it can be very useful. Successful examples we've seen include:

  • Custom security interception, where the complexity of security checks required is beyond the capability of the standard J2EE security infrastructure

  • Debugging and profiling aspects for use during development

  • Interceptors that send emails to alert administrators or users of unusual scenarios

Application-specific aspects can be a powerful way of removing the need for boilerplate code across many methods.

Spring AOP integrates transparently with the Spring BeanFactory concept. Code obtaining an object from a Spring BeanFactory doesn't need to know whether or not it is advised. As with any object, the contract will be defined by the interfaces the object implements.

The following XML stanza illustrates how to define an AOP proxy:

<bean id="myTest"
	class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="proxyInterfaces">
		<value>org.springframework.beans.ITestBean</value>
	</property>
	<property name="interceptorNames">
		<list>
			<value>txInterceptor</value>
			<value>target</value>
		</list>
	</property>
</bean>

Note that the class of the bean definition is always the AOP framework's ProxyFactoryBean, although the type of the bean as used in references or returned by the BeanFactory getBean() method will depend on the proxy interfaces. (Multiple proxy methods are supported.) The "interceptorNames" property of the ProxyFactoryBean takes a list of String. (Bean names must be used rather than bean references, as new instances of stateful interceptors may need to be created if the proxy is a "prototype", rather than a singleton bean definition.) The names in this list can be interceptors or pointcuts (interceptors and information about when they should apply). The "target" value in the list above automatically creates an "invoker interceptor" wrapping the target object. It is the name of a bean in the factory that implements the proxy interface. The myTest bean in this example can be used like any other bean in the bean factory. For example, other objects can reference it via <ref> elements and these references will be set by Spring IoC.

It's also possible to construct AOP proxies programmatically without using a BeanFactory, although this is more rarely used:

TestBean target = new TestBean();
DebugInterceptor di = new DebugInterceptor();
MyInterceptor mi = new MyInterceptor();
ProxyFactory factory = new ProxyFactory(target);
factory.addInterceptor(0, di);
factory.addInterceptor(1, mi);
// An "invoker interceptor" is automatically added to wrap the target
ITestBean tb = (ITestBean) factory.getProxy();

We believe that it's generally best to externalize the wiring of applications from Java code, and AOP is no exception.

Spring's most direct competitor in terms of its AOP capabilities is Jon Tirsen's Nanning Aspects (http://nanning.codehaus.org).

I feel that the use of AOP as an alternative to EJB for delivering enterprise services is growing in importance. This will be an important focus of Spring, moving forward.


MVC web framework

Spring includes a powerful and highly configurable MVC web framework.

Spring's MVC model is most similar to that of Struts. A Spring Controller is similar to a Struts Action in that it is a multithreaded service object, with a single instance executing on behalf of all clients. However, we believe that Spring MVC has some significant advantages over Struts. For example:

  • Spring provides a very clean division between controllers, JavaBean models, and views.

  • Spring's MVC is very flexible. Unlike Struts, which forces your Action and Form objects into concrete inheritance (thus taking away your single shot at concrete inheritance in Java), Spring MVC is entirely based on interfaces. Furthermore, just about every part of the Spring MVC framework is configurable via plugging in your own interface. Of course we also provide convenience classes as an implementation option.

  • Spring MVC is truly view-agnostic. You don't get pushed to use JSP if you don't want to; you can use Velocity, XLST or other view technologies. If you want to use a custom view mechanism - for example, your own templating language - you can easily implement the Spring View interface to integrate it.

  • Spring Controllers are configured via IoC like any other objects. This makes them easy to test, and beautifully integrated with other objects managed by Spring.

  • The web tier becomes a thin layer on top of a business object layer. This encourages good practice. Struts and other dedicated web frameworks leave you on your own in implementing your business objects; Spring provides an integrated framework for all tiers of your application.

As in Struts 1.1, you can have as many dispatcher servlets as you need in a Spring MVC application.

The following example shows how a simple Spring Controller can access business objects defined in the same application context. This controller performs a Google search in its handleRequest() method:

public class GoogleSearchController
		implements Controller {

	private IGoogleSearchPort google;

	private String googleKey;

	public void setGoogle(IGoogleSearchPort google) {
		this.google = google;
	}

	public void setGoogleKey(String googleKey) {
		this.googleKey = googleKey;
	}

	public ModelAndView handleRequest(
				HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		String query = request.getParameter("query");
		GoogleSearchResult result =
			// Google property definitions omitted...

			// Use google business object
			google.doGoogleSearch(this.googleKey, query,
				start, maxResults, filter, restrict,
				safeSearch, lr, ie, oe);

		return new ModelAndView("googleResults", "result", result);
	}
}

In the prototype this code is taken from, IGoogleSearchPort is a GLUE web services proxy, returned by a Spring FactoryBean. However, Spring IoC isolates this controller from the underlying web services library. The interface could equally be implemented by a plain Java object, test stub, mock object, or EJB proxy, as discussed below. This controller contains no resource lookup; nothing except code necessary to support its web interaction.

Spring also provides support for data binding, forms, wizards and more complex workflow.

A good introduction to the Spring MVC framework is Thomas Risberg's Spring MVC tutorial (http://www.springframework.org/docs/MVC-step-by-step/Spring-MVC-step-by-step.html). See also "Web MVC with the Spring Framework" (http://www.springframework.org/docs/web_mvc.html).

If you're happy with your favourite MVC framework, Spring's layered infrastructure allows you to use the rest of Spring without our MVC layer. We have Spring users who use Spring for middle tier management and data access but use Struts, WebWork or Tapestry in the web tier.


Implementing EJBs

If you choose to use EJB, Spring can provide important benefits in both EJB implementation and client-side access to EJBs.

It's now widely regarded as a best practice to refactor business logic into POJOs behind EJB facades. (Among other things, this makes it much easier to unit test business logic, as EJBs depend heavily on the container and are hard to test in isolation.) Spring provides convenient superclasses for session beans and message driven beans that make this very easy, by automatically loading a BeanFactory based on an XML document included in the EJB Jar file.

This means that a stateless session EJB might obtain and use a collaborator like this:

import org.springframework.ejb.support.AbstractStatelessSessionBean;

public class MyEJB extends AbstractStatelessSessionBean
			implements MyBusinessInterface {
	private MyPOJO myPOJO;

	protected void onEjbCreate() {
		this.myPOJO = getBeanFactory().getBean("myPOJO");
	}

	public void myBusinessMethod() {
		this.myPOJO.invokeMethod();
	}
}

Assuming that MyPOJO is an interface, the implementing class - and any configuration it requires, such as primitive properties and further collaborators - is hidden in the XML bean factory definition.

We tell Spring where to load the XML document via an environment variable definition named ejb/BeanFactoryPath in the standard ejb-jar.xml deployment descriptor, as follows:

<session>
	<ejb-name>myComponent</ejb-name>
	<local-home>com.test.ejb.myEjbBeanLocalHome</local-home>
	<local>com.mycom.MyComponentLocal</local>
	<ejb-class>com.mycom.MyComponentEJB</ejb-class>
	<session-type>Stateless</session-type>
	<transaction-type>Container</transaction-type>

	<env-entry>
		<env-entry-name>ejb/BeanFactoryPath</env-entry-name>
		<env-entry-type>java.lang.String</env-entry-type>
		<env-entry-value>/myComponent-ejb-beans.xml</env-entry-value></env-entry>
	</env-entry>
</session>

The myComponent-ejb-beans.xml file will be loaded from the classpath: in this case, in the root of the EJB Jar file. Each EJB can specify its own XML document, so this mechanism can be used multiple times per EJB Jar file.

The Spring superclasses implement EJB lifecycle methods such as setSessionContext() and ejbCreate(), leaving the application developer to optionally implement the Spring onEjbCreate() method.


Using EJBs

Spring also makes it much easier to use, as well as implement EJBs. Many EJB applications use the Service Locator and Business Delegate patterns. These are better than spraying JNDI lookups throughout client code, but their usual implementations have significant disadvantages. For example:

  • Typically code using EJBs depends on Service Locator or Business Delegate singletons, making it hard to test.
  • In the case of the Service Locator pattern used without a Business Delegate, application code still ends up having to invoke the create() method on an EJB home, and deal with the resulting exceptions. Thus it remains tied to the EJB API and the complexity of the EJB programming model.
  • Implementing the Business Delegate pattern typically results in significant code duplication, where we have to write numerous methods that simply call the same method on the EJB.

For these and other reasons, traditional EJB access, as demonstrated in applications such as the Sun Adventure Builder and OTN J2EE Virtual Shopping Mall, can reduce productivity and result in significant complexity.

Spring steps beyond this by introducing codeless business delegates. With Spring you'll never need to write another Service Locator, another JNDI lookup, or duplicate methods in a hand-coded Business Delegate unless you're adding real value.

For example, imagine that we have a web controller that uses a local EJB. We'll follow best practice and use the EJB Business Methods Interface pattern, so that the EJB's local interface extends a non EJB-specific business methods interface. (One of the main reasons to do this is to ensure that synchronization between method signatures in local interface and bean implementation class is automatic.) Let's call this business methods interface MyComponent. Of course we'll also need to implement the local home interface and provide a bean implementation class that implements SessionBean and the MyComponent business methods interface.

With Spring EJB access, the only Java coding we'll need to do to hook up our web tier controller to the EJB implementation is to expose a setter method of type MyComponent on our controller. This will save the reference as an instance variable like this:

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {
	this.myComponent = myComponent;
}

We can subsequently use this instance variable in any business method.

Spring does the rest of the work automatically, via XML bean definition entries like this. LocalStatelessSessionProxyFactoryBean is a generic factory bean that can be used for any EJB. The object it creates can be cast by Spring to the MyComponent type automatically.

<bean id="myComponent"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">

	<property name="jndiName"><value>myComponent</value></property>
	<property name="businessInterface"><value>com.mycom.MyComponent</value></property>
</bean>

<bean id="myController"
	class = "com.mycom.myController"
>
	<property name="myComponent"><ref bean="myComponent"/></property>
</bean>

There's a lot of magic happening behind the scenes, courtesy of the Spring AOP framework, although you aren't forced to work with AOP concepts to enjoy the results. The "myComponent" bean definition creates a proxy for the EJB, which implements the business method interface. The EJB local home is cached on startup, so there's only a single JNDI lookup. Each time the EJB is invoked, the proxy invokes the create() method on the local EJB and invokes the corresponding business method on the EJB.

The myController bean definition sets the myController property of the controller class to this proxy.

This EJB access mechanism delivers huge simplification of application code:

  • The web tier code has no dependence on the use of EJB. If we want to replace this EJB reference with a POJO or a mock object or other test stub, we could simply change the myComponent bean definition without changing a line of Java code

  • We haven't had to write a single line of JNDI lookup or other EJB plumbing code as part of our application.

Benchmarks and experience in real applications indicate that the performance overhead of this approach (which involves reflective invocation of the target EJB) is minimal, and undetectable in typical use. Remember that we don't want to make fine-grained calls to EJBs anyway, as there's a cost associated with the EJB infrastructure in the application server.

We can also apply the same approach to remote EJBs, via the similar org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean factory bean. However, we can't conceal the RemoteExceptions on the business methods interface of a remote EJB.


Testing

As you've probably gathered, I and the other Spring developers are firm believers in the importance of comprehensive unit testing. We believe that it's essential that frameworks are thoroughly unit tested, and that a prime goal of framework design should be to make applications built on the framework easy to unit test.

Spring itself has an excellent unit test suite. Our unit test coverage as of 1.0 M1 is over 75%, and we're hoping to go above 80% unit test coverage for 1.0 RC1. We've found the benefits of test first development to be very real on this project. For example, it has made working as an internationally distributed team extremely efficient, and users comment that CVS snapshots tend to be stable and safe to use.

We believe that applications built on Spring are very easy to test, for the following reasons:

  • IoC facilitates unit testing

  • Applications don't contain plumbing code directly using J2EE services such as JNDI, which is typically hard to test

  • Spring bean factories or contexts can be set up outside a container

The ability to set up a Spring bean factory outside a container offers interesting options for the development process. In several web application projects using Spring, work has started by defining the business interfaces and integration testing their implementation outside a web container. Only after business functionality is substantially complete is a thin layer added to provide a web interface.


Who's using Spring?

Although Spring is a relatively new project, we already have an impressive and growing user community. There are already many production applications using Spring. Users include a major global investment bank (in a large-scale project), well-known dotcoms, several web development consultancies, a health care company, and academic institutions.

Many users use all parts of Spring, but some use components in isolation. For example, a number of users begin by using our JDBC or other data access functionality.


Roadmap

Our main priority is getting Spring to release 1.0, later this year. However, we also have some longer-term goals.

One major enhancement scheduled for 1.0 final is source-level metadata support, which will be primarily intended for (but not restricted to) use with our AOP framework. This will enable C#-style attribute-driven transaction management, and make declarative enterprise services very easy to configure for typical usage. Attribute support will be included in the Spring 1.0 final release, and will be designed to integrate with JSR-175, when it is released.

Beyond 1.0, some likely areas of enhancement include:

  • JMS support via a comparable abstraction to our JDBC and transaction support
  • Support for dynamic reconfiguration of bean factories
  • The ability to expose web services
  • IDE and other tool support

As an agile project, we are primarily driven by user requirements. So we don't develop features that no one has a use for, and we listen carefully to our user community.


Summary

Spring is a powerful framework that solves many common problems in J2EE.

Spring provides a consistent way of managing business objects and encourages good practices such as programming to interfaces, rather than classes. The architectural basis of Spring is an Inversion of Control container based around the use of JavaBean properties. However, this is only part of the overall picture: Spring is unique in that it uses its IoC container as the basic building block in a comprehensive solution that addresses all architectural tiers.

Spring provides a unique data access abstraction, including a simple and productive JDBC framework that greatly improves productivity and reduces the likelihood of errors. Spring's data access architecture also integrates with Hibernate and other O/R mapping solutions.

Spring also provides a unique transaction management abstraction, which enables a consistent programming model over a variety of underlying transaction technologies, such as JTA or JDBC.

Spring provides an AOP framework written in standard Java, which provides declarative transaction management and other enterprise services to be applied to POJOs or - if you wish - the ability to implement your own custom aspects. This framework is powerful enough to enable many applications to dispense with the complexity of EJB, while enjoying some of the key services traditionally associated with EJB.

Spring also provides a powerful and flexible MVC web framework that can be integrated into the overall IoC container.


More information

See the following resources for more information about Spring:

We're doing our best to improve the Spring documentation and examples. We also pride ourselves on excellent response rates to queries on the forms and mailing lists. We hope to welcome you into our community soon!


About the Author

Rod Johnson has over seven years experience as a Java developer and architect and has worked with J2EE since the platform emerged. He is the author of Expert One-on-One J2EE Design and Development (Wrox, 2002) and has contributed to several other books on J2EE. He is currently writing another book on J2EE architecture for Wiley. Rod serves on two Java specification committees and is a regular conference speaker. He currently works in the UK as a consultant.


阅读全文
0 0

相关文章推荐

img
取 消
img