Albert Einstein once said, "Everything should be made as simple
as possible, but not simpler." Indeed, the pursuit of scientific
truth has been all about simplifying a theory's underlying
assumptions so that we can deal with the issues that really matter.
The same can be said for enterprise software development.
A key to simplifying enterprise software development is to provide
an application framework that hides complexities (such as
transaction, security, and persistence) away from the developers. A
well-designed framework promotes code reuse, enhances developer
productivity, and results in better software quality. However, the
current EJB 2.1 framework in J2EE 1.4 is widely considered poorly
designed and over-complicated. Unsatisfied with the EJB 2.1
framework, Java developers have experimented with a variety of
other approaches for middleware services delivery. Most noticeably,
the following two frameworks have generated a lot of developer interest and
positive feedback. They are posed to become the frameworks of choice for
future enterprise Java applications.
The Spring
framework is a popular but non-standard open source framework.
It is primarily developed by and controlled by Interface21 Inc. The
architecture of the Spring framework is based upon the Dependency
Injection (DI) design pattern. Spring can work standalone or with
existing application servers and makes heavy use of XML
configuration files.
The EJB
3.0 framework is a standard framework defined by the Java
Community Process (JCP) and supported by all major J2EE vendors. Open
source and commercial implementations of pre-release EJB 3.0
specifications are already available from JBoss and Oracle.
EJB 3.0 makes heavy use of Java annotations.
These two frameworks share a common core design philosophy: they
both aim to deliver middleware services to loosely coupled plain old Java objects
(POJOs). The framework "wires" application
services to the POJOs by intercepting the execution context or
injecting service objects to the POJO at runtime. The POJOs
themselves are not concerned about the "wiring" and have little
dependency upon the framework. As a result, developers can focus on
the business logic, and unit test their POJOs without the
framework. In addition, since POJOs do not need to inherit from
framework classes or implement framework interfaces, the developers
have a high degree of flexibility to build inheritance structures and
construct applications.
However, while sharing an overall philosophy, the two
frameworks use very different approaches to deliver POJO services.
While numerous books and articles have been published to compare
either Spring or EJB 3.0 to the old EJB 2.1, no serious study
has been done to compare Spring to EJB 3.0. In this article, I
will examine some key differences behind the Spring and EJB 3.0
frameworks, and discuss their pros and cons. The topics covered in
this article also apply to other lesser-known enterprise middleware
frameworks, as they all converge on the "loosely coupled POJO"
design. I hope this article will help you choose the best
framework for your needs.
Vendor Independence
One of the most compelling reasons for developers to choose the
Java platform is its vendor independence. EJB 3.0 is an open
standard designed for vendor independence. The EJB 3.0
specification is developed and supported by all major open source
and commercial vendors in the enterprise Java community. The EJB
3.0 framework insulates developers from application server
implementations. For instance, while JBoss's EJB 3.0 implementation
is based on Hibernate, and Oracle's is based on TopLink, developers
need to learn neither Hibernate- nor TopLink-specific APIs to get
their applications running on JBoss and Oracle. Vendor independence
differentiates the EJB 3.0 framework from any other POJO middleware
frameworks available today.
However, as many EJB 3.0 critics are quick to point out, the EJB 3.0
specification has not yet reached the final release at the time of
this writing. It will likely be another one to two years before EJB 3.0 is
widely adopted by all major J2EE vendors. But even if your
application server does not yet support EJB 3.0 natively, you can
still run EJB 3.0 applications in the server by downloading and
installing an "embeddable" EJB 3.0 product. For instance, the JBoss
embeddable EJB 3.0 product is open source and runs in any J2SE-5.0-compatible environment (i.e., in any Java application server).
It is currently under beta testing. Other vendors may also soon
release their own embeddable EJB 3.0 products, especially for the
"data persistence" part of the specification.
On the other hand, Spring has always been a non-standard
technology and will remain that way in the foreseeable future.
Although you can use the Spring framework with any application
server, Spring applications are locked into both Spring itself and
the specific services you choose to integrate in Spring.
While the Spring framework is an open source project, Spring
has a proprietary XML format for configuration files and a
proprietary programming interface. Of course, this type of lock-in
happens to any non-standard product; it is not specific to Spring.
But still, the long-term viability of your Spring application
depends on the health of the Spring project itself (or Interface21
Inc., which hires most of Spring's core developers). In addition, if
you use any of the Spring-specific services, such as the Spring
transaction manager or Spring MVC, you are locked into those APIs
as well.
Spring applications are not agnostic to back-end service
providers, either. For instance, for data persistence services, the
Spring framework comes with different DAO and template helper
classes for JDBC, Hibernate, iBatis, and JDO. So if you need to
switch the persistence service provider (e.g., change from JDBC to
Hibernate) for a Spring application, you will need to refactor your
application code to use the new helper classes.
Service Integration
From a very high level, the Spring framework sits above
application servers and service libraries. The service integration
code (e.g., data access templates and helper classes) resides in
the framework and is exposed to the application developers. In
contrast, the EJB 3.0 framework is tightly integrated into the
application server and the service integration code is encapsulated
behind a standard interface.
As a result, EJB 3.0 vendors can aggressively optimize the
overall performance and developer experience. For instance, in
JBoss's EJB 3.0 implementation, when you persist an Entity Bean
POJO using the EntityManager, the underlying Hibernate
session transaction is automatically tied to the calling method's
JTA transaction, and it commits when the JTA transaction commits.
Using a simple @PersistenceContext annotation (see
later in this article for an example), you can even tie the
EntityManager and its underlying Hibernate transaction
to an application transaction in a stateful session bean. The
application transaction spans across multiple threads in a session
and it is very useful in transactional web applications, such as
multi-page shopping carts. The above simple and integrated
programming interface is made possible due to the tight integration
between the EJB 3.0 framework, Hibernate, and Tomcat inside of JBoss.
A similar level of integration is also archived between Oracle's EJB
3.0 framework and its underlying Toplink persistence service.
Another good example of integrated services in EJB 3.0 is
clustering support. If you deploy an EJB 3.0 application in a
server cluster, all of the fail-over, load-balancing, distributed
cache, and state replication services are automatically available
to the application. The underlying clustering services are hidden
behind the EJB 3.0 programming interface and they are completely
transparent to EJB 3.0 developers.
In Spring, it is more difficult to optimize the interaction
between the framework and the services. For instance, in order to
use Spring's declarative transaction service to manage
Hibernate transactions, you have to explicitly configure the Spring
TransactionManager and Hibernate
SessionFactory objects in the XML configuration file.
Spring application developers must explicitly manage transactions
that span across several HTTP requests. In addition, there is no
simple way to leverage clustering services in a Spring
application.
Flexibility in Service Assembly
Since the service integration code in Spring is exposed as part
of the programming interface, application developers have the
flexibility to assemble services as needed. This feature enables
you to assemble your own "lightweight" application servers. A
common usage of Spring is to glue Tomcat together with Hibernate to
support simple database-driven web applications. In this case,
Spring itself provides transaction services and Hibernate provides
persistence services--this setup creates a mini application
server in itself.
EJB 3.0 application servers typically do not give you that kind
of flexibility in picking and choosing on the services you need.
Most of the time, you get a set of prepackaged features, some of
which you might not need. However, if the application server
features a modular internal design, as JBoss does, you might be
able to take it apart and strip out the unnecessary features. In
any case, it is not a trivial exercise to customize a full-blown
application server.
Of course, if the application scales beyond a single node, you
would need to wire in services (such as resource pooling, message
queuing, and clustering) from regular application servers. The
Spring solution would be just as "heavyweight" as any EJB 3.0
solution, in terms of the overall resource consumption.
In Spring, the flexible service assembly also makes it easy to
wire mock objects, instead of real service objects, into the
application for out-of-the-container unit testing. In EJB 3.0
applications, most components are simple POJOs, and they can be
easily tested outside of the container. But for tests that involve
container service objects (e.g., the persistence
EntityManager), in-container tests are recommended, as
they are easier, more robust, and more accurate than the mock
objects approach.
XML Versus Annotation
From the application developer's point view, Spring's
programming interface is primarily based upon XML configuration files
while EJB 3.0 makes extensive use Java annotations. XML files can
express complex relationships, but they are also very verbose and
less robust. Annotations are simple and concise, but it is hard to
express complex or hierarchical structures in annotations.
Spring and EJB 3.0's choices of XML or annotation depend upon the
architecture behind the two frameworks: since annotations can only
hold a fairly small amount of configuration information, only a
pre-integrated framework (i.e., most of plumbing has been done in
the framework) can make extensive use of annotations as its
configuration option. As we discussed, EJB 3.0 meets this
requirement, while Spring, being a generic DI framework, does
not.
Of course, as both EJB 3.0 and Spring evolve to learn from each
other's best features, they both support XML and annotations to
some degree. For instance, XML configuration files are available in
EJB 3.0 as an optional overriding mechanism to change the default
behavior of annotations. Annotations are also available to
configure some Spring services.
The best way to learn the differences between the XML and
annotation approaches is through examples. In the next several
sections, let's examine how Spring and EJB 3.0 provide key services
to applications.