Guidelines for Spring Development


This book has covered both the how and the why of Spring development. Let's summarize some of the most important criteria in the why category that will help you develop successful Spring applications.

Technology Choices

Let's begin by looking at some of the key technology choices you might make in typical applications.

Spring Versus "Old J2EE"

If you're reading this chapter, you've presumably decided to use Spring. However, you still may be considering where you should use Spring: in particular, how your Spring usage should relate to the traditional EJB component model.

Important 

Spring provides a compelling alternative to the EJB programming model in a large proportion of J2EE applications. If you're implementing a web application — even a high-end transactional web application — you're probably better off without EJB. If it uses a single database, you can probably do perfectly well with a servlet engine such as Tomcat or Jetty, without the added complexity of an application server. Spring's declarative transaction management provides a programming model that is portable between low- and high-end environments, so you can always switch to a full-blown application server if you ever need to, while doing the simplest deployment that can possibly work in the meantime.

Even if you have multiple transactional resources, you may well not need EJB. You can use Spring's declarative transaction management over JTA as a replacement for EJB CMT, although in this case we recommend that you do use a high-end application server that provides robust distributed transaction management (although a standalone transaction manager such as ObjectWeb's JOTM is an option).

Important 

Spring gives you a choice as to whether you need to use a full-blown application server. If you really need an application server, Spring will let you get at its full power; if you don't really need its capabilities, Spring will help you dispense with the unnecessary complexity and baggage it brings, but without closing off the option of deploying your code to a full-blown J2EE application server in the future if you ever need to.

If you do choose to use EJB — or if it is imposed on you by managerial fiat — you can still use most of Spring's capabilities behind an EJB facade. It is good practice in EJB 2.x applications to make EJBs only coarse-grained facades, with the bulk of the work being done by POJOs behind them. Spring provides an excellent way of managing those POJOs, and its superclasses for implementing EJBs both make this good usage practice easier and help to catch some common EJB programming errors, such as failure to honor the implicit contract required of EJBs but not expressed in any interface (such as the need for an ejbCreate() method). A Spring application is likely to be much closer to the eventual EJB 3.0 model than an EJB 2.x architecture.

As using EJB 2.x entity beans is hardly a viable architectural choice, you will still want to use Spring's data access integration behind an EJB facade if you choose to use EJB. Note that this will work perfectly if you choose to use EJB container-managed transactions.

Distributed Versus Local Objects

Minimize unnecessary calling out of process. It adds complexity and performance overhead. It does not usually increase scalability. Co-locate business objects and their callers if possible.

This is, of course, a matter of prudent design rather than a consequence of using Spring. Spring in fact provides a powerful lightweight remoting infrastructure that supports multiple protocols.

As with transaction management, Spring remoting can increase potential for code reuse, by isolating callers from dependency on specific remoting infrastructures — they need simply to express a dependency on the business methods interface of the potentially remote service, and leave Spring's client-side infrastructure to perform the necessary lookup and remoting service steps. Of course, it's still dangerous to rely on location transparency: There are many issues to be considered in transferring object graphs over the wire.

Important 

Distribution should be seen as a facade layer on top of a well-defined service layer. Distribution considerations should not infect the whole of your design, as has traditionally occurred with transfer objects and orthodox J2EE architecture.

Java Versus Other Languages?

As of Spring 1.3 you can use a variety of languages, including Groovy and Beanshell, to implement any object managed by a Spring container. As other objects depend on that object by interface, they are unaware of its implementation language. (Yet another example of the benefits of the pluggability that an IoC container provides.) You can configure a scripted object using Spring Dependency Injection or Setter Injection, and you can also apply the full power of Spring's AOP framework to any scripted object — for example, providing it with declarative transaction management or custom auditing.

Why would you choose to implement an object in a language other than Java? Some indications are:

  • You need dynamic reloading. Spring's support of scripting languages allows this, so long as the external contract (interfaces) of the script doesn't change.

  • The code is naturally much more concise in the scripting language than in Java, or the intent is much clearer. For example, if you can use Groovy closures to good effect, there may be a real case for writing an object in Groovy instead of Java.

On the other hand, reasons not to use another language are:

  • Better IDE support for Java, such as code completion: This gap may narrow in the future as Eclipse plug-ins and other IDE support are developed for Groovy and other languages.

  • Better performance: Even scripting languages that compile to Java byte code are likely to impose more performance overhead than pure Java objects. Spring's dynamic scripting support also means that any scripted object will be an AOP proxy, with a number of introductions, although this amounts to only a modest overhead.

  • Consistency: Clearly the expectation will be that objects in a Java platform application will be written in Java. You need to have good reason to cut against that expectation.

The choice is up to you: As usual, Spring doesn't constrain you.

Layer by Layer

Let's now look through the architectural tiers of a typical Spring application, discussing best practices in organizing whole applications and the individual tiers.

A Layered Architecture

We recommend — and Spring facilitates — the use of a layered architecture. Note that while logical layering is essential to clean application design, physical layering is not.

In a typical Spring application, the layers will be as follows:

  • Presentation tier: Most often a web tier, but a good architecture should accommodate a choice of different presentations. We include remoting as a form of presentation in the present discussion.

  • Business services layer: The entry point for the application's services. The business services layer is typically responsible for delimiting transactions within which code in lower architectural tiers will execute.

  • DAO interfaces: A layer of interfaces used to find and save persistent domain objects. This is called the Repository in Eric Evans' Domain Driven Development (Addison-Wesley, 2003).

  • Persistent domain objects: Persistent objects such as Product or Performance that make up a model of the application domain, and may contain behavior as well as state.

  • Databases and other EIS tier services: The application uses these for persistence and integration.

Of course such layering is not Spring-specific; it reflects good architectural practice. However, Spring significantly helps to facilitate good practice.

Let's summarize best practices for each layer, beginning closest to the database.

Data Access and Integration

The most common requirement is working with one or more relational databases — usually, with one. The key choice here is between the use of O/R mapping and a relational approach to data.

Important 

In general, if you can use O/R mapping, you should probably do so, as it will save time and effort by freeing you from the significant effort of writing SQL to load and store your objects, and may help to give you a better domain model. (Application developers should spend time on their domain, rather than plumbing concerns such as writing mundane SQL.) However, you should not be dogmatic, and insist on using O/R mapping where it's a poor fit.

Indications that O/R mapping won't work in a particular application include:

  • A complex legacy data model, where there is no natural O/R mapping

  • The need to use stored procedures heavily (for example, because of a requirement to update the database only through stored procedures)

  • Many set-based operations

  • Limited scope for caching

If these considerations apply, you are probably better off using a relational, SQL-based approach, with Spring JDBC or iBATIS SQL Maps.

In some applications you will need to mix both SQL-based and O/R mapping approaches. If you do mix and match, remember the following issues, which are crucial to preserving data integrity:

  • JDBC updates or deletes can invalidate the first- or second-level cache of the ORM product.

  • ORM updates that have not been flushed to the database can result in JDBC queries returning stale results. Explicitly flush the ORM tool's first-level cache if you need to query. But where possible, if you must query the same data, do it within the ORM tool, which should manage flushing automatically.

If an ORM tool and JDBC code are used to access different tables — as often happens — these issues are not a concern.

Although the application developer is responsible for ensuring correct semantics, Spring does help significantly with the plumbing of mixing and matching: for example, making it easy to use the same JDBC connection in JDBC and Hibernate code, even without the use of JTA.

Important 

Never use JDBC directly. Consider an ORM tool, or iBATIS SQL Maps. If you need low-level relational access, use Spring JDBC. Avoid direct use of the JDBC API, which will cost time and allow greater potential for errors.

Whatever O/R tool you choose — and the choice is up to you, not imposed by Spring — we recommend that you use Spring's data access integration and the recommended DAO interface approach to work with data. This has many benefits:

  • It decouples business service objects from API details, meaning that business objects operate at a higher level of abstraction and are more testable.

  • It insulates your application against changes in data access technology. For example, if you want to migrate from Hibernate 2 today to JSR-220 persistence once it is released, you will be able to do so without extensive change: While you will inevitably need to take semantic differences into account, the bulk of the work will be limited to reimplementing a well-defined layer of DAO interfaces.

  • Spring's data access exception hierarchy is not only a key element in the decoupling, but helps to clean up your code through being highly informative.

We recommend that you configure your O/R mapping tool using Spring's local factories, rather than through JCA (which adds complexity without adding real value in this case). For example, it's usually best to configure Hibernate using Spring's LocalSessionFactoryBean.

Persistent Domain Model

Persistent domain objects model things and concepts in your domain. Because J2EE has a poor history on object orientation, it's important to make the following notes about domain objects:

  • Remember the object part. Don't fall into the traditional J2EE trap of viewing them as dumb bit buckets. They should encapsulate their state, and may contain behavior.

  • Domain objects should use inheritance as necessary. Forget another antipattern from the dark days of EJB 1.x and 2.x entity beans, where inheritance was off-limits.

Your domain model is inevitably partly determined by the capabilities of your persistence strategy. For example, some use of inheritance is often appropriate, but deep inheritance may well pose a problem for O/R mapping solutions — at least, if you care about having an idiomatic relational model, rather than tables that contain data for several subclasses. (Database views may sometimes help here.)

If you can, try to avoid making your persistent domain objects depend on your persistence strategy — for example, by implementing callback interfaces such as Hibernate's net.sf.hibernate.Lifecycle or JDO's javax.jdo.InstanceCallbacks. (For example, if you are using Hibernate, user types are valuable in such cases, externalizing Hibernate-specific code from domain objects themselves.)

Business Services Layer

In a traditional J2EE architecture, this layer would be implemented in stateless session beans. In a Spring application, it will be implemented as POJOs, which can enjoy a variety of declarative services — both from Spring, and custom.

The major issues here are transaction management and thread management.

Transaction Management

There are a number of important choices around transaction management. With a Spring-based solution you can make any of these choices, in any combination. However, as transaction management is a crucial architectural issue, you need to understand the issues behind the decisions you make.

Programming model

There's a fundamental choice between programmatic transaction management (writing code to drive transactions through some API) and declarative transaction management (using a framework that can automatically delimit transactions around methods or other boundaries). Spring supports both.

Important 

We recommend declarative transaction management in most cases. If you can avoid writing code to handle the transaction aspect, it's a win. Given appropriate design, most things can be done with declarative transaction management.

If you choose programmatic transaction management, Spring's transaction abstraction — and especially the TransactionTemplate helper class — will significantly simplify the programming model whatever underlying transaction infrastructure you use.

If you've chosen declarative transaction management, you have the choice between two major implementing technologies: EJB CMT and Spring transaction management. EJB CMT works well, but of course incurs the various negatives attached to EJB as a technology overall.

Important 

In general, we recommend Spring's declarative transaction management, rather than EJB CMT. It's simpler to set up and more powerful (through the unique rollback rule concept), and it decouples your code from JNDI and JTA, maximizing potential for reuse and testing.

If you want to use MDBs, using CMT makes more sense: In this case, you can get message acknowledgment.

Spring's own message consumption capabilities should eventually provide an additional choice here.

Underlying infrastructure

There is also a choice of underlying infrastructure for your transactions: for example, JTA global transactions, or JDBC, Hibernate, JDO, or other local transactions on a specific API.

If you choose to use EJB, you are tied to JTA. If you choose Spring transaction management — whether programmatic or declarative — you have a consistent programming model over any supported transaction infrastructure (including all those just mentioned).

Important 

Don't automatically use JTA. Far fewer applications need distributed transactions than J2EE developers have traditionally assumed. Distributed transactions have a high cost in complexity and performance, so it's wise to avoid them if possible.

While JTA is not intended purely for distributed transactions — and using JTA won't make your transactions distributed if you're using just a single database — it is a relatively complex API and infrastructure combination, and using local transactions may be more appropriate.

Fortunately, with Spring you don't need to make a permanent choice between local and global transactions. You can preserve the same programming model, changing only configuration, not code, and confidently start with a simple transaction infrastructure until it's proven that you need greater power and complexity.

Threading Issues

Another important issue to consider in business services is the threading model.

Important 

Choose the simplest threading model that can possibly work. Given that service layer objects are normally stateless, this is a single shared instance. Normally this is not difficult to achieve, as instance variables will be configured via the IoC container before the object's business methods can be invoked, and thus are not read-write at runtime. Instance variables of types such as DAOs — such as the DataSources and Session factories that DAOs use — are normally threadsafe, although of course you need to be aware of the issues.

It's normally necessary only to pool resources, such as database connections, rather than instances of business objects.

We believe that the traditional insistence on using a pool of service objects, which EJB enforces in the case of stateless session beans, is not necessary. Unnecessary pooling can actually be harmful if you want a single instance rather than a pool of instances — which, for example, may all have their own copies of reference data. It also typically slightly reduces performance, without increasing scalability.

However, you can add pooling transparently if you find a valid case for it, using Spring's pooling target sources, discussed in Chapter 4.

Presentation Tier

The presentation tier should sit on a well-defined business object layer. When this is the case, the complexity of the presentation tier is reduced, and it becomes feasible to have multiple different presentation tiers on one application, which may be a business requirement.

Important 

The presentation tier should be thin. It should contain only the code required to process user interactions, invoke business services, and display the results of processing to the user.

Web Application Presentation

All web applications should use a web application framework to provide structure and encourage best practice.

Choose between Spring MVC's other web technologies based on merit and how they fit your requirements. While Spring MVC is a good, flexible MVC solution, if there are compelling reasons for choosing another framework — such as substantial in-house experience with Struts, or the existence of an in-house library around another web framework — you can easily integrate with a Spring-managed middle tier, and still enjoy the bulk of the benefits that Spring offers. Choice of web application framework is not always a purely technical choice.

Important 

Remember that you can pick and choose what Spring modules you wish to use: Although Spring is potentially a one-stop shop, it's also a set of modules that you can choose individually.

Approach the choice between view technologies, such as JSP and Velocity, using similar decision criteria as when choosing between web application frameworks. Don't automatically choose JSP. Remember that in any large project, web tier views are likely to be maintained by HTML developers rather than Java developers, and their preferences are also very important. One of the key benefits of using Spring MVC is that it is designed to make controllers and models independent of any view technology. Thus, with it, you could reasonably expect to be able to mix JSP and Velocity views within an application without introducing any architectural inconsistency, and you can add different types of views (such as PDF generators) without affecting controller or model code.

Remoting

Don't build remoting concerns into the fabric of your application — for example, propagating transfer objects through multiple architectural layers.

Instead, think of remoting as a layer over a well-defined service model. In some cases, you may simply want to export the services represented by the service layer objects. Spring enables you to export remote services from any POJO it manages, over a range of protocols. However, in many cases, you will need to add a remote facade to address issues such as the following:

  • Serializability: Remote arguments and return values must be serializable. Alternatively, you may want to transform data into XML representation, for example for SOAP document literal encoding.

  • Granularity: "Chatty" remote calling is very harmful to performance and must be avoided.

Thus you may want to offer coarse-grained operations that replace multiple fine-grained operations.

  • Data disconnection: There may be issues in "disconnecting" or "detaching" persistent objects from the persistence store. These issues vary between APIs such as Hibernate and JDO, but with any API you will need to consider the amount of associated data that has been retrieved because some degree of lazy loading is usually essential for performance.

Spring's remoting services are accomplished using its AOP framework under the covers. This can be particularly useful if you want to add an interception chain around remote invocation — a concept offered by many remoting technologies (although in a less flexible way), but lacking in EJB.

You can also export remote services using remote EJBs, with a Spring context behind the EJB facade. This is a good choice if you want RMI-based remoting — and it is significantly facilitated by using Spring to help consume the EJB services on the client side, without the need for any EJB or JNDI-specific code.

image from book
Web Tier Design Principles

Ensure clean separation of the different types of objects in your web tier.

— Controller objects should process user input and invoke the necessary business services.

— Model objects contain data necessary to display the results of this processing — they do not perform processing themselves.

— Views are responsible purely for rendering model data.

image from book

Structuring Applications

Let's now look at some issues relevant to all architectural tiers.

Overall Issues

We recommend the following guidelines in general:

  • Practice good OO design. Spring is primarily an enabling technology from the design perspective. You are responsible for using a good OO design; Spring is less likely to get in your way than traditional J2EE APIs and the EJB component model. In particular:

    • Follow best practice by programming to interfaces rather than classes. While, unlike some IoC containers, Spring doesn't enforce this, doing so does make it easier to leverage the full power of Spring's AOP framework. But more importantly, programming to interfaces is an OO best practice that an IoC container such as Spring makes very much easier. You will no longer lose some of the value of using interfaces because you need to code singletons and factory objects; Spring eliminates the need for such code, and you should reap the full benefit.

    • Thus, the Strategy design pattern becomes easy to use and is often a good choice. Such use of delegation is usually better and more flexible than reliance on concrete inheritance.

  • Don't use your own configuration mechanisms such as XML files, database lookups, or properties files. Spring provides a wide range of configuration choices: not just XML, but the ability to define objects in Java, database tables, and so on.

  • Avoid unnecessary dependence on Spring APIs. There should normally be no need to depend on Spring APIs for configuration. (Using Spring abstractions, such as data access APIs, is a different matter.) Part of the point of Dependency Injection is to minimize the need for dependency on any container. Become familiar with capabilities of Spring's IoC container that can help to minimize such dependencies, such as Method Injection (discussed in Chapter 3).

Spring Configuration Issues

Let's now look at some issues specific to Spring usage.

Different Styles of Dependency Injection

Spring supports three types of Dependency Injection:

  • Setter Injection, in which objects are configured using their JavaBean properties.

  • Constructor Injection, in which objects are configured using constructor arguments.

  • Method Injection, in which the container overrides abstract or concrete methods, typically to perform a container lookup, without the need to write Spring-specific code.

It's even possible to mix and match all three of these — for example, using Constructor Injection to populate mandatory dependencies, and using Setter Injection to populate optional dependencies.

The choice between these is up to you. Making a wrong initial choice for a particular object is of no great consequence, as you can always refactor, and Spring will localize the impact of that so that it doesn't affect any objects that depend on the refactored object.

Most of the Spring team tend to prefer Setter Injection in general, but the choice is really up to the application developer.

Method Injection is typically used only to avoid container dependence — for example, when obtaining unique instances of non-threadsafe objects. Method Injection requires dynamic byte code generation using CGLIB, so it adds a degree of complexity at runtime that is not incurred by the other forms of Dependency Injection.

Autowiring

As we discussed when we talked about Dependency Injection, Spring can do the work of resolving the object dependencies of any object.

Auto wiring can be done by name, in which case properties are resolved by method name (for example, a dataSource property would be satisfied by a bean with name dataSource); or by type (for example, all dependencies of type javax.sql.DataSource are satisfied by a single bean definition of that type).

Autowiring by type tends to be more useful and intuitive. However, it works only in cases where there is a single object implementing each target type. For example, if there are two DataSources, auto wiring by type will not work.

Auto wiring by name can cope with multiple instances of the target type (such as transactionalDataSource and readOnlyDataSource).

You can set autowire mode at the individual bean definition level, or at the context definition level, and still retain the ability to override the default for individual bean definitions. Spring's default is for no autowiring, requiring explicit wiring.

On the plus side, autowiring:

  • Is less verbose than explicit configuration.

  • Keeps itself up to date. For example, if you add more properties, they will automatically be satisfied without the need to update bean definition(s) of that class.

On the negative side, autowiring:

  • Is somewhat magical. The container's behavior at runtime isn't described in your configuration, merely implied. However, Spring's policy of raising a fatal error on any ambiguity means that unwanted effects are unlikely.

  • Is less self-documenting than explicit wiring.

  • Cannot be used for simple configuration properties, rather than object dependencies. However, you can still explicitly set configuration properties on autowired beans.

The Spring team tends to use explicit configuration in general. However, it is a matter of personal preference, and you and your team should at least be aware of the autowiring features Spring offers. The Spring philosophy is to make good practices easy to follow but to avoid imposing choices on developers using Spring.

Important 

As with choice of Dependency Injection style, this is a choice that doesn't need to be permanent. Bean definitions can be changed in isolation without impacting other bean definitions. (You can adopt autowiring at context level, however.) However, a degree of consistency is normally a good idea in a project, in this area, as with most others.

Autoproxying

One of the most important decisions you can make is whether you will use "autoproxying" rather than per-bean AOP configuration. Autoproxying allows you to specify a single interceptor chain for use with multiple objects. It supports per-instance and shared instance models, so this does not automatically mean the same interceptor instances for all autoproxied objects.

The most obvious win here concerns J2SE 5.0 annotations, which can be used to drive Spring declarative transaction management and other features, as of Spring 1.2. Released only in late 2004, J2SE 5.0 is too new to be an option in typical enterprise projects in early 2005, but its acceptance will gradually increase. If you are using J2SE 5.0, we recommend using autoproxying for transaction management.

Important 

Even if you are not using J2SE 5.0, you should understand how Spring's autoproxying features work and use them where appropriate. Essentially, autoproxying enables you to apply consistent AOP advice to a number of objects. The objects themselves will be defined as POJOs.

Use of autoproxying works particularly well in a team, as a few aspect or infrastructure experts can deliver the necessary aspects and autoproxy configuration, leaving application developers to get on with the job of implementing business objects as POJOs. This is a clean separation of responsibility, as well as concerns.

Again, however, autoproxying is slightly magical. It is, however, possible to control ordering of autoproxied advisors, by making them implement the Ordered interface. Thus ordering can also be addressed by an infrastructure team within a larger application development team.

Inheritance of Bean Definitions

Another powerful means of simplifying Spring configurations is to "inherit" bean definitions. This can help greatly to reduce the volume of configuration. For example, you might:

  • Use an inherited definition to set common properties on a View.

  • Use an inherited definition to set up AOP proxying information.

We strongly encourage understanding and using this mechanism. Duplication — whether in configuration or code — is bad and should be minimized.

Breaking Up Configuration

Try to avoid having one large XML configuration file. This will create unnecessary contention in your source control system, and is likely to become unwieldy.

Another powerful motivation for breaking up XML configuration is that you can isolate those parts of your configuration that may change in different environments. For example, if you have DataSource and transaction manager definitions in a single file, you could then have two versions: a test one that uses a local DataSource and local transaction manager, and a production one that uses a JNDI DataSource and Spring's JTA transaction manager. These definitions will have the same bean names in each file. On the other hand, the definitions of your application objects, such as DAOs and business objects — which of course will depend on such configuration — will be the same in all environments. Thus you can test the bulk of your configuration even outside the eventual deployed environment, which will help speed up testing.

You can also use a base context definition to apply declarative services such as autoproxy infrastructure. For example, the Spring jPetStore sample application shows the use of attributes to drive transactions. This is accomplished through a base context definition that does not normally need to be edited as the application is enhanced. Consider such base definitions as defining the services of your container. You are effectively creating your own container, and Spring gives you the tools — and examples — to do it.

What Should Be an Aspect?

As Spring provides an AOP framework, you now have the choice between implementing some types of functionality using OOP or AOP.

Note 

This not purely a Spring issue: There are a variety of other AOP frameworks that will also give you this choice. Hence this section is not really Spring-specific.

Declarative middleware is an obvious application for AOP, especially transaction management and declarative security checks. We believe that AOP is clearly the best way to address such requirements in most applications.

The trickier problems concern custom aspects. And here you are not dealing purely with technical considerations. You should consider the following:

  • Will your team understand an AOP approach to this problem? The flip side of this is that sometimes using AOP can localize tricky problems and simplify the job of many developers.

  • Is the AOP approach clearly superior to the OOP approach? Does it modularize a large amount of code that would otherwise be scattered and duplicated? In this case, any "risk" around using AOP may be far outweighed by the inevitable development and maintenance effort associated with a non-AOP approach.

  • Is my object model to blame? Will refactoring improve things, without AOP?

If you have a compelling case for using AOP, don't allow fear about a new paradigm to stop you. But don't model things as aspects just because you can.

Important 

Code duplication is a good indication that you should use AOP. Through modularizing crosscutting concerns, AOP provides a powerful way to address such code duplication.

Testing Your Applications

With Spring, you can test your applications as follows:

  • Unit testing

  • In-context testing

  • Higher-level forms of testing that you would traditionally perform in any case

    Important 

    Appropriate use of Spring greatly enhances testability compared to traditional J2EE design.

Let's look at these stages of testing in turn, and how Spring can affect them (for the better).

Unit Testing

Unit testing means testing objects in isolation. The essence of unit testing is what it leaves out. If your unit tests unnecessarily rely on real, rather than mocked or stubbed, collaborators, they will be unduly complicated, and you are almost certain to encounter duplication when there isn't a clear match between a unit test and the code it tests.

Important 

Unit testing should be done without any container, Spring or other, and without any further dependencies such as a database or JNDI environment. Part of the point of Dependency Injection and AOP is to enable true unit testing; you should take advantage of that important bonus of this programming model.

You should unit test Spring applications using plain JUnit tests. You do not need infrastructure such as Cactus that is designed to run inside an application server or other special runtime environment.

Your unit tests should be designed for speed. Your entire unit test suite should be automated, and you should be able to run all unit tests in a few minutes at most. It should be realistic to run all unit tests before committing any change to source control.

Please see J2EE without EJB for a thorough discussion of unit testing techniques for lightweight J2EE applications.

Note 

Contrary to some widely held misconceptions, commitment to thorough unit testing — and design to facilitate it — does not mean reduced commitment to other forms of testing.

In-Context Integration Testing

There are two problems with the traditional reliance on testing inside an application server in traditional J2EE: It's not true unit testing (if that's what is intended) and it's too slow. Deployment to a container takes precious time; doing it every time you make a code change is a huge obstacle to productivity. The cumulative effect of many interruptions — even relatively short interruptions — is a huge loss of productivity.

With Spring or another Dependency Injection container that offers a non-invasive programming model, the first of these issues is taken care of. You can and should do true unit testing without any container in sight.

The second issue can also be addressed with Spring. What if you want to do a form of integration testing where you test objects working together in a subsystem, working against either stub resources or real resources such as databases? In this case, you can start a Spring container in an ordinary JUnit test case. Starting a Spring container requires very little overhead: Spring is merely a class library from the point of view of a JUnit test. There is a huge difference between the sub-second startup time of a typical Spring context and the seconds required to start up an application server and the deployment units needing testing.

Spring provides support for such test-time context startup in the org.springframework.test package. This enables you to create JUnit test cases that are themselves configured by Dependency Injection, being populated by objects from a Spring IoC container. Even better, there is a superclass (org.springframework.test.AbstractTransactionalSpringContextTests) that automatically creates a transaction for each test method before rolling it back after completion. This can be a very powerful technique when you want to do integration testing, actually hitting your database, but don't want the overhead of deployment to a J2EE application server or traditional setup and teardown scripts to keep your database clean. The overhead of running such tests is modest, as by default the application context will be cached throughout the execution of your own test suite, in the event that initializing of certain objects or frameworks such as Hibernate is a noticeable startup cost. (While the startup cost of the Spring container itself will be negligible, that may not be the case for some of the application objects and framework objects it may contain.)

Higher Levels of Testing

Of course there is much more to testing than unit testing and integration testing. Later stages of testing are beyond the scope of this book. In this case, you will do what you would have done without using Spring: Spring can't help you too much, beyond simple things such as making it easier to invoke remote services to drive tests against a deployed container. For any project, you need a proper test plan and, usually, a testing team to ensure it's executed. While thorough unit testing is extremely important to success, it's not the be all and end all of testing.



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