Comparison of Spring and EJB3
Comparison of Spring and EJB3
1 Introduction
Software development professionals today are faced with a bewildering array of technology choices. It is not always easy to decide which technologies are most appropriate to employ in solving any particular business problem. In the area of enterprise Java development, there has recently been quite a bit of debate regarding the Spring framework and the Enterprise Java Bean 3.0 specification. It can be hard to find clear and honest advice through the cacophony of opinion.
Still, the question of the relative strengths and weaknesses of each is a question I encounter frequently in my consulting work. But while there is plenty of information on Spring and EJB 3.0 individually, there is very little objective information that compares the two. This article will examine both technologies in relationship to one another with the goal of helping you understand the similarities and differences between them.
Spring and EJB are not the same thing. Most importantly, Spring is an implementation while EJB 3.0 is a specification. But they do have some areas of overlap, for example both provide a mechanism to deliver middleware services to Java applications. Spring was developed as a reaction against EJB and this makes comparison between the two natural. Particularly now that a new version of EJB is available, it is a good time to reevaluate how well EJB 3.0 has addressed the shortcomings of previous versions. And, to the extent that is has done so successfully, what does Spring continue to offer above and beyond EJB?
It should also be noted that EJB specifically addresses the development of distributed components whereas this emphasis is missing from the Spring definition.
This article is the first in a two-part series; each article will look at different characteristics of Spring and EJB 3. The characteristics I chose are ones that are particularly meaningful within the context of enterprise application development. I will explore each characteristic using a flight booking example application, which has a rich domain model and illustrates well enterprise challenges such as concurrency, conversations, transactionality, messaging, and scheduling. I've also used unit tests to determine whether Spring and EJB 3.0 offer comparable functionality.
The Definition
Before launching into a more in-depth comparison it is helpful to consider for a moment what Spring and EJB each claim to be. The Spring Web site defines Spring as "a layered Java/J2EE application framework." And EJB claims to be "a component architecture for the development and deployment of object-oriented, distributed, enterprise-level applications" (J2EE glossary).
First, we can tell from these definitions that both intend to operate in the enterprise Java development space. But Spring claims to be an application framework whereas EJB an architecture. While what is meant by architecture varies greatly, a general definition could be the big decisions that are hard to change, or the structure of a software system. A framework implies support whereas an architecture implies decision. It may be helpful to keep these nuances in mind during the comparison. And it should also be noted that EJB specifically addresses the development of distributed components whereas this emphasis is missing from the Spring definition
2 Persistence
Persistence is a critical component of any enterprise application. And not surprisingly both Spring and EJB 3.0 provide robust support for it. True to its design philosophy, Spring doesn't re-implement a persistence framework but rather integrates with many popular ones including JDBC, Hibernate, JDO, iBatis, and (as of Spring 2.0) the Java Persistence API (JPA). With the EJB 3.0 specification entity beans have been replaced with the JPA.
JPA aims to provide a simplified, light-weight, object-relational mapping framework. This specification (a separate document within the EJB 3.0 specification) defines interfaces for interacting with the persistence provider and for mapping entities to a relational database.
Persistence―Functional Comparison
To illustrate the comparability in support between Spring and EJB 3.0 consider the domain model for the flight booking application shown in Figure 1.
Figure 1. Flight Booking Domain Model. The flight booking domains illustrated many interesting ORM concepts such as composition, bi-directionality, and ternary associations.
The unit test below verifies that a ticket can be created, associated with existing flights, seats assigned, and saved to a database:
public void testSave()
{
Ticket ticket = TicketMother.create();
Flight outboundFlight = flightDAO.findById(1);
Flight returnFlight = flightDAO.findById(2);
ticket.setOutboundFlight(outboundFlight);
ticket.setReturnFlight(returnFlight);
ticket.getPassenger().setFlightDetails(outboundFlight,
new PassengerFlightDetails(getSeatForFlight(outboundFlight, "2A")));
ticket.getPassenger().setFlightDetails(returnFlight,
new PassengerFlightDetails(getSeatForFlight(returnFlight, "2B")));
ticketDAO.save(ticket);
ticket = ticketDAO.findById(ticket.getId());
assertEquals("John", ticket.getPassenger().getFirstName());
assertEquals("2A", ticket.getPassenger().getDetailsForFlight(ticket.getOutboundFlight())
.getSeat().getNumber());
}
This test can be run successfully against both Spring and EJB 3.0 implementations indicating that the two technologies have functionally equivalent support for basic ORM persistence. The two implementations are also very similar. I have chosen to illustrate the Spring implementation with Hibernate since it is the most common ORM provider used in conjunction with Spring and because JPA support is not available in the current production release of Spring. A Spring/Hibernate implementation of the TicketDAO is class is illustrated below:
public class TicketSpringDAO extends HibernateDaoSupport implements TicketDAO{
public Ticket save(Ticket ticket){
getHibernateTemplate().saveOrUpdate(ticket);
return ticket;
}
…
}
And the corresponding EJB 3.0 implementation is seen here:
@Stateless
public class TicketEJBDAO implements TicketDAO{
@PersistenceContext(unitName = "flightdb")
private EntityManager em;
public Ticket save(Ticket ticket){
em.persist(ticket);
return ticket;
}
...
}
Obviously these implementations are both very similar. In the Spring implementation the details of working with Hibernate are abstracted away by the HibernateDaoSupport class, which provides the Hibernate plumbing and assists in working with the Spring Hibernate template and dependencies. In the EJB 3.0 implementation the entity manager is automatically injected by the container.
The Hibernate Session and the JPA Entity Manager are roughly equivalent but there are a couple of important differences to keep in mind. The Hibernate session is an entity cache as well an interface into the ORM engine. In JPA these two concepts are separated. The persistence context functions as the cache while the entity manager functions at the interface into the ORM engine.
It is also interesting to notice the different approaches towards mapping persistence entities to their relational equivalents. The Spring/Hibernate mapping is expressed in the following XML mapping file:
<hibernate-mapping package="org.jug.flight.domain" default-lazy="false">
<class name="Ticket">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="status" type="Status" />
<many-to-one name="outboundFlight" column="outbound_flight" />
<many-to-one name="returnFlight" column="return_flight" />
<many-to-one name="passenger" column="passenger_id" cascade="all" />
</class>
</hibernate-mapping>
JPA applications often choose to use annotations to express this mapping because it reduces the configuration required, keeps the mapping data close to the objects it modifies, and enhances visibility. This approach is illustrated below:
@Entity
public class Ticket implements Serializable{
@Id
@GeneratedValue
private long id;
private Status status;
private double price;
@ManyToOne(cascade = CascadeType.PERSIST)
private Passenger passenger;
private Flight outboundFlight;
private Flight returnFlight;
...
}
As you can see, Spring/Hibernate and EJB 3.0/JPA offer roughly functionally equivalent support for persistence via ORM.
Persistence―Non-Functional Comparison
The primary difference between Spring and EJB 3.0's support for persistence may at first appear to be compatibility with different persistence frameworks. Spring provides support for many different ORM implementations. In EJB, only JPA is explicitly supported. But it is important to remember that JPA is merely a specification and many different providers support JPA including Hibernate, Kodo, and TopLink. Table 1 lists the default JPA provider for several popular EJB 3.0 application servers:
Table 1. JPA Providers Supported by Leading App Servers.
Application Server JPA Implementation
JBoss Hibernate
BEA Kodo (OpenJPA)
Oracle TopLink
Glassfish TopLink
In this sense both technologies offer a choice in persistence providers. If you need to use provider-specific features that are not part of the JPA specification then you can certainly do so in your EJB application but you may sacrifice platform cross-compatibility.
Another important concept when working with ORM tools is caching. The cache is important when considering Spring and EJB 3.0 because in order for multiple components to collaborate on a single unit of work this cache must be shared between them. In Spring this is typically accomplished by applications attaching the session (and transaction) to the thread using thread local variables. Although Spring offers classes to make implementing this easier it still needs to be addressed by the development team. In EJB 3.0 by contrast the persistence context is propagated automatically on the transaction.
Object models are often rich and complex graphs. It would be inefficient if every time a persistent entity was retrieved from the database if all of its relationships were automatically fetched. To address this ORM providers often provide functionality such as lazy initialization, which defers the execution of particular database operations until the data is actually needed. This prevents a lot of unnecessary and expensive database calls but can complicate application logic quite a bit. If the cache associated with a lazily initialized collection is closed when the collection is accessed then exceptions are thrown.
The approach taken by Spring/Hibernate is to open and close the cache around view creation. Spring provides an OpenSessionInViewFilter (and interceptor) to simplify this, but it must be configured. The EJB 3.0 specification adds an interesting scope to its persistence context that allows the lifetime of the cache to be bound to the lifetime of a stateful session bean. This is named an extended persistence context and is specified by annotating the persistence context in the stateful session bean. This can simplify working with long application transactions and lazily loaded collections.
Persistence―Summary
Because JPA supports pluggable persistence providers and because Spring supports those same providers directly these two approaches are very comparable. Table 2 highlights the major comparison points.
Table 2. Summation of Persistence Comparison Between Spring and EJB 3.0.
Feature Spring EJB 3.0
Simple ORM Persistence √ √
Implementation Hibernate, JPA, JDO, TopLink, iBatis JPA (providers include Hibernate, Kodo and Toplink)
JDBC Support √ X
Mapping XML, Annotations Annotations, XML
Cache Propagation Thread local Transaction
Extended cache scoping Open session in view Extended persistence context
Standard √ (If using JPA) √
3 Transaction Management
Another critical factor in many enterprise applications is transaction management. This is because in high-volume applications many users may be attempting to access the same data simultaneously. And in single- and multi-user situations you may often want a particular operation to either entirely succeed or fail.
Transaction Management―Functional Comparison
To illustrate the functionality capabilities of Spring and EJB 3.0 to provide transactionality consider the case of purchasing a ticket in the flight booking scenario (see Figure 2).
Figure 2. Purchase Ticket Sequence Diagram. If any step in the purchase ticket sequence fails then the system should be left in a consistent state.
The unit test shown below verifies that the purchase ticket operation functions in a transactional manner:
public void testPurchaseTicket_DebitFailure() throws Exception
{
// Creates a pending ticket
Ticket ticket = createTicket();
// Store original ticket count from DB
int count = ticketDAO.findAll().size();
// Setup a credit authorizer which will throw an exception on debit
setupMockAuthorizer(true);
try
{
bookingAgent.purchaseTicket(ticket, true);
fail("System failure was not thrown as was intended");
}
catch (InsufficientFundsException e)
{
// Correct behavior since we're testing transactional semantics
}
// Verify transaction rolled-back
assertEquals(count, ticketDAO.findAll().size());
}
This test can also be successfully executed against both Spring and EJB 3.0 implementations. The Spring implementation is shown below:
public Ticket purchaseTicket(Ticket ticket)
{
creditAuthorizer.authorize(ticket.getPrice(), null);
ticket.setStatus(Status.PURCHASED);
ticketDAO.save(ticket);
creditAuthorizer.debit(ticket.getPrice());
return ticket;
}
Compare this to the EJB 3.0 implementation:
public Ticket purchaseTicket(Ticket ticket)
{
creditAuthorizer.authorize(ticket.getPrice(), null);
ticket.setStatus(Status.PURCHASED);
ticketDAO.save(ticket);
creditAuthorizer.debit(ticket.getPrice());
return ticket;
}
Notice that they are exactly the same and that neither has to explicitly deal with transaction management! This illustrates well the value of declarative programming. Developers can write code that focuses on the business problem, and cross-cutting concerns such as transactionality can be modularized and applied across an application. In this case both Spring and EJB 3.0 offer comparable functionality in dealing with transactions and both succeed modularizing this functionality in a way that it doesn't clutter business logic.
Transaction Management―Non-Functional Comparison
Import aspects of transaction management to consider include demarcation, propagation, and isolation. The process of bounding a unit of work with transactional semantics is termed demarcation. And if multiple components are collaborating on a single unit of work, this transaction should be shared (propagated) between them. Lastly, isolation levels allow developers to tune the degree of isolation that the transaction has from other transactions.
In Spring, transaction demarcation, propagation, and isolation are all declared through its AOP facilities. Transactional proxies can be explicitly defined in its XML configuration files or can be applied using aspect semantics. The approach of wiring in a transactional proxy is illustrated below:
<bean id="bookingAgent" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="bookingAgentSpring" />
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="bookingAgentSpring" class="org.jug.flight.booking.spring.BookingAgentSpring">
<property name="flightDAO" ref="flightDAO" />
<property name="ticketDAO" ref="ticketDAO" />
<property name="screener" ref="screener" />
</bean>
EJB 3.0 on the other hand applies transactional semantics automatically on all public methods of session beans so no special configuration is required. Developers can tune the transaction propagation levels via annotations or XML:
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Ticket purchaseTicket(Ticket ticket)
{
...
}
The EJB 3.0 specification declares transaction isolation to be resource-manager specific, thus there is no standard way to express this requirement in EJB 3.0.
Spring offers integration with many different types of transaction APIs including JDBC, Hibernate, and JTA. EJB 3.0 only supports JTA transactions. The choice of transaction API is important in two primary cases. First, not all containers (such as Tomcat) support JTA, although all EJB containers do. Second, in order to join multiple resources (such as JDBC and JMS) in a single distributed transaction, a JTA implementation is required. If you require JTA and are running in an environment that does not provide a JTA implementation you can consider open-source implementations of JTA such as the Java Open Transaction Manager (JOTM).
Transaction Management―Summary
Both Spring and EJB 3.0 offer an integrated approach to transaction management that is compatible with their persistence mechanisms (see Table 3).
Table 3. Equivalent Transaction Management Functionality in Both Spring and EJB 3.0.
Feature Spring EJB 3.0
Declarative Transactions √ √
Programmatic Transactions √ √
Demarcation AOP Session bean methods
Supported Transaction Types JDBC, Hibernate, JTA JTA
Support for Distributed Transactions √ (With JTA) √
Configuration XML Transactional by default, override with annotations or XML
Standard √ (With JTA) √
4 Statefulness
One of the more characteristic differences between Spring and EJB 3.0 is their respective approaches to managing state. State is an important element to many applications because a user's interaction with the system typically involves a series of steps with cumulative meaning. For example, in the context of the example flight booking application, while booking a ticket a customer navigates between browsing for flights, selecting seat assignments, and providing payment information. As the user navigates between these steps the choices made in previous steps are implicit in the context of the conversation
Recognizing the important role state plays in many applications, EJB provides a first level construct, the Stateful Session Bean (SFSB), dedicated to managing a stateful conversation. With SFSBs subsequent invocations of a bean share the bean instance and thus the same state. SFSBs are also designed with issues of scalability and fail-over in mind although the implementation of many of these concerns are left to specific vendor implementations. Spring does not offer any direct equivalent to SFSBs although several approaches exist for accomplishing the same result.
Statefulness―Functional Comparison
To illustrate the approaches toward state offered by Spring and EJB 3.0 consider the sequence of steps involved in booking a ticket (see Figure 3).
Figure 3. Book Ticket Sequence Diagram. Passengers book tickets through a series of interactions with a booking agent. These steps are conversational and the context of the conversation (the state) evolves with each step.
The following unit test verifies that the booking agent can be interacted with in a stateful manner:
public void testPurchaseTicket_UseCase() throws Exception
{
// Begin purchase ticket conversation
FlightCriteria flightCriteria = new FlightCriteria("DFW", "AUS", DateUtilities
.parseDate("2006-01-15"));
bookingAgent.bookRoundTripTicket(flightCriteria);
// Select outbound flight
List outboundFlights = bookingAgent.listOutboundFlights();
assertEquals(3, outboundFlights.size());
bookingAgent.selectOutboundFlight(selectFlight(outboundFlights, 1));
// Select return flight
List<Flight> returnFlights = bookingAgent.listReturnFlights();
assertEquals(3, returnFlights.size());
bookingAgent.selectReturnFlight(selectFlight(returnFlights, 2));
// Select passenger
Passenger passenger = new Passenger("Rod", "Coffin");
bookingAgent.selectPassenger(passenger);
// Select seats
bookingAgent.selectOutboundSeat(selectSeat(bookingAgent.retrieveCurrentTicket()
.getOutboundFlight(), "1B"));
bookingAgent.selectReturnSeat(selectSeat(bookingAgent.retrieveCurrentTicket()
.getReturnFlight(), "1A"));
// Purchase ticket
Ticket ticket = bookingAgent.purchaseTicket();
assertTrue(ticket.getId() != 0);
}
Both Spring and EJB 3.0 can satisfy this unit test. The Spring implementation is shown below:
public class BookingAgentSpring implements BookingAgent
{
private FlightCriteria outboundCriteria;
private FlightCriteria returnCriteria;
private Ticket ticket;
public Ticket bookRoundTripTicket(FlightCriteria flightCriteria)
{
outboundCriteria = flightCriteria;
returnCriteria = reverseCriteria(flightCriteria);
ticket = new Ticket();
ticketDAO.save(ticket);
return ticket;
}
...
}
In order for Spring-managed beans to contain state, instances cannot be shared between callers. To accomplish this, the scope of stateful beans in Spring should be configured as prototype. This means that each time a bean is retrieved from a bean factory a new instance is created.
<bean id="bookingAgentSpring" scope=”prototype”
class="org.jug.flight.booking.spring.BookingAgentSpring">
...
</bean>
Compare with the EJB 3.0 implementation:
@Stateful
public class BookingAgentEJB implements BookingAgent
{
private FlightCriteria outboundCriteria;
private FlightCriteria returnCriteria;
private Ticket ticket;
public Ticket bookRoundTripTicket(FlightCriteria flightCriteria)
{
outboundCriteria = flightCriteria;
returnCriteria = reverseCriteria(flightCriteria);
ticket = new Ticket();
ticketDAO.save(ticket);
return ticket;
}
...
}
In both cases the booking agent can store state and participate with the caller in a conversational manner. So it is possible to create stateful behavior with both Spring and EJB 3.0 but the approaches and implications of these approaches differ significantly with one another.
Statefulness - Non-Functional Comparison
The examples above illustrate how stateful objects could be implemented in both Spring and EJB 3.0. Although functionally equivalent there is a significant difference in how state is managed. In the Spring implementation the caller stores the complete booking agent (see Figure 4), whereas in the EJB 3.0 implementation the caller is merely storing a proxy to the booking agent (see Figure 5). Because the caller in the EJB 3.0 version is only holding onto a proxy, the SFSB and its associated state exists within―and is managed by―the server.
Figure 4. Client Managed State. One approach to implementing stateful beans with Spring is to store the stateful component in the HttpSession managed by the web container.
Figure 5. EJB Managed State. Clients interact with EJB components via proxies. This allows the component's state to be managed by the EJB container.
There are several advantages to the EJB approach with SFSBs. First, SFSB are designed to optimize server performance by allowing SFSBs to be swapped between memory and persistence storage. This allows application servers to balance resources to achieve optimal performance. Secondly, SFSBs can be run in a separate process from the caller. This might be performed to allow tiers of an application to be scaled independently or for security purposes. Thirdly, SFSBs may also implement the optional SessionSynchronization interface to in order to rollback bean state in response to a transaction rollback event.
One of the major criticisms typically heaped on SFSBs is that they do not perform or scale as well as stateless architectures. In response to this, the Spring framework is primarily a stateless framework. But given that state is an important part of many applications, there has to some options for expressing state. Typical approaches include putting state in the database, the HTTP Session, or in an in-memory cache. In the example above, the stateful Spring booking agent could be stored in either of these vehicles. But each of these options has its own disadvantages, as shown in Table 4.
Table 4. Alternatives to Stateful Session Beans in Spring.
SFSB Alternative Advantages Disadvantages
Database Shared by all application servers. Usually the least scalable tier of an application. If using a second-level cache then data must be replicated.
HTTP Session Simple to use. Data must be replicated and is not transaction aware.
Memory Cache Shared by all application servers, potentially more efficient then using the database. Data must be replicated and is not transaction aware.
The scalability argument applied against SFSBs can also be applied to each of these three alternatives to SFSBs (as shown in Table 4). In addition you must spend considerable development effort re-implementing functionality already provided by SFSBs. These tradeoffs should be considered seriously when deciding upon your application's approach to state.
Both Spring and EJB 3 offer hooks that allow developers to tie into the lifecycle of managed beans. Spring provides the InitializingBean and DisposableBean interfaces, which provide hooks to notify beans after they have been initialized by Spring and before destruction. Similarly, EJB components can optionally receive notifications after creation, before destruction, before passivation, and after activation.
Lastly, the issue of how to synchronize the conversational state of a bean with the status of a transaction is one that must be resolved, whether using SFSBs or an alternative. By default, when a transaction rolls back, all transactional resources participating in the transaction (such as database and messaging systems) roll back as well. But the state of the beans involved will not be rolled back. If these beans are stateful then they will be out of sync with the persistence data store. EJB 3.0 SFSBs using container-managed transactions can implement the SessionSynchronization interface to be notified of transactional events. Spring does not offer equivalent functionality but it could be handled in some custom way by the application.
Statefulness―Summary
Both Spring and EJB 3.0 enable stateful applications. In an EJB 3.0 application the primary mechanism for accomplishing this is the use of SFSBs. Spring is primarily a stateless architecture but can facilitate statefulness through prototype beans. Applications using this approach need to consider its implications for performance and scalability and will need some other method of addressing the issues handled by SFSBs.
Table 5. Differences in State-handling Between Spring and EJB 3.0.
Feature Spring EJB 3.0
Stateful construct Prototype (instance) beans Stateful Session Bean
Instance Management Singleton, prototype, request, session, global session Instance per lookup
Lifecycle Management √ (Initialization/Destruction) √ (Creation/Destruction, Activation/Passivation)
Transaction awareness X √ (SessionSynchronization interface)
Standard N/A √
Messaging
Enterprise applications often don't exist in isolation but must interact with other applications and services running within and outside a company. Messaging is a common approach to this problem and support is provided by both Spring and EJB 3.0. There are two basic operations in messaging: sending and receiving. And, typically, there are two methods of receiving messages: explicit message retrieval and automatic notification (listening).
Messaging―Functional Comparison
To explore how Spring and EJB 3.0 each implement the sending of Java Message Service (JMS) messages, consider the sequence of steps I've outlined for the purchasing of an airline ticket (see Figure 1).
A Spring implementation of the TsaScreener object is shown below:
public class TsaPassengerScreenerClientSpring implements TsaPassengerScreener
{
private JmsTemplate jmsTemplate;
private Queue queue;
public void screenPassenger(final Passenger passenger)
{
jmsTemplate.send(queue, new MessageCreator()
{
public Message createMessage(Session session) throws JMSException
{
return session.createObjectMessage(passenger);
}
});
} ...
}
The necessary configuration to support this implementation is shown below:
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate" />
<bean id="tsaQueue" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate">
<ref bean="jndiTemplate" />
</property>
<property name="jndiName">
<value>java:comp/env/queue/tsaQueue</value>
</property>
</bean>
<bean id="tsaQCF" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate">
<ref bean="jndiTemplate" />
</property>
<property name="jndiName">
<value>java:comp/env/queue/tsaQCF</value>
</property>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<constructor-arg>
<ref bean="tsaQCF" />
</constructor-arg>
</bean>
<bean id="tsaPassengerScreener"
class="org.jug.flight.booking.spring.TsaPassengerScreenerClientSpring">
<property name="jmsTemplate" ref="jmsTemplate" />
<property name="tsaQueue" ref="tsaQCF" />
</bean>
Compare this to the EJB 3.0 implementation:
@Stateless
public class TsaPassengerScreenerClientEJB implements TsaPassengerScreener
{
@Resource(mappedName = "java:/ConnectionFactory")
QueueConnectionFactory factory;
@Resource(mappedName = "queue/tsaQueue")
Queue queue;
public void screenPassenger(Passenger passenger)
{
try
{
QueueConnection con = factory.createQueueConnection();
QueueSession session = con.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
ObjectMessage msg = session.createObjectMessage(passenger);
QueueSender sender = session.createSender(queue);
sender.send(msg);
session.close();
con.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Figure 1. Purchase Ticket Sequence Diagram. In this example the purchase ticket use case has been expanded to send a message to the TSA with the details of the passenger who is booking a flight.
Notice that the Spring implementation uses the JmsTemplate helper class to simplify working with the JMS API to send the message. Also, notice that in both cases the container injects the JMS resources (queue connection factory and queue) into the screener object. I'll explore injection more later, in the section on dependency management. But at this point it is interesting to note the Spring configuration is more verbose then the EJB equivalent.
After the passenger details message has been sent the TSA needs to receive and process it. Both Spring and EJB 3.0 can support notification of objects when incoming messages arrive. These objects are referred to as message listeners. A Spring implementation of a message-driven POJO (MDP) is shown below:
public class TsaPassengerScreenerMDP implements MessageListener
{
public void onMessage(Message message)
{
ObjectMessage objectMessage = (ObjectMessage) message;
try
{
Passenger passenger = (Passenger) objectMessage.getObject();
// Process message
}
catch (JMSException e)
{
e.printStackTrace();
}
}
}
The necessary configuration to support this implementation is shown below:
<bean id="tsaListener" class="org.jug.flight.booking.spring.TsaPassengerScreenerMDP" />
<bean id="listenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="concurrentConsumers" value="5" />
<property name="connectionFactory" ref="tsaQCF" />
<property name="destination" ref="tsaQueue" />
<property name="messageListener" ref="tsaListener" />
</bean>
Compare this to the EJB 3.0 implementation of a message-driven bean (MDB):
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/tsaQueue") })
public class TsaPassengerScreenerMDB implements MessageListener
{
public void onMessage(Message message)
{
ObjectMessage objectMessage = (ObjectMessage) message;
try
{
Passenger passenger = (Passenger) objectMessage.getObject();
// Process message
}
catch (JMSException e)
{
e.printStackTrace();
}
}
}
As shown above, both Spring and EJB 3.0 enable the sending and receiving of JMS messages. And interestingly the implementations of message consumption via a Spring MDP and an EJB MDB are quite similar. Additionally, both solutions allow the volume of concurrent message processing to be throttled allowing you to tune the amount of resources allocated within an application to message processing. In the case of EJBs this is a container-specific function whereas with Spring it is a function of the message listener container.
Another important feature of enterprise messaging solutions is their ability to participate in distributed transactions. Often multiple resources will be involved in message processing (typically messaging and database resources) and the work performed on these resources should be performed atomically. Both Spring and EJB 3.0 allow messaging activities to participate in distributed transactions. In EJB 3.0 however, in order to have message acknowledgement tied to successful completion of a transaction, you will need to use container-managed transactions, otherwise messages that have been previously processed may be redelivered. The same can be accomplished in Spring when using JTA transactions and when the message container is defined as "sessionTransacted." In either case you could choose to allow message redelivery and handle this case programmatically.
A more telling difference in messaging support between Spring and EJB is the type of objects that can be configured to listen for incoming messages. In EJB message listeners must be EJB components whereas in Spring any POJO can potentially be activated in response to incoming messages. Both require that the message listener implement an interface and both support the javax.jms.MessageListener interface for JMS message listeners. However, since the 2.1 version of the EJB specification EJB MDBs can support other messaging interfaces such as javax.resource.cci.MessageListener which allow MDBs to be activated by a JCA CCI connector.
Messaging―Non-Functional Comparison
Most Java applications use JMS to interact with an underlying message provider. This is the case with both Spring and EJB 3.0. But Spring provides templates that assist with retrieving the necessary message resources from the application server (via JNDI) and for creating, sending, and receiving messages. EJB 3.0 allows message resources to be injected into a class via its dependency injection facilities, but it does not provide any facility equivalent to Spring's template to simplify the use of the JMS API. Spring also provides message converters which can automate the conversion of Java objects to message content.
In terms of standardization both approaches support the Java standard for messaging, JMS. In terms of implementation however MDBs are a JCP standard whereas Spring MDPs are not. But although MDPs are not a standard they are portable as the Spring runtime can run in any JSE or JEE container.
Messaging―Summary
Table 1 summarizes the key differences between Spring and EJB 3.0’s messaging support.
Table 1. Messaging Features in Spring and EJB 3.0.
Feature Spring EJB 3.0
Message Listening √ √
Listener Types Any POJOimplementing the javax.jms.MessageListener interface MDBs implementing any interface appropriate to its messaging type, usually javax.jms.MessageListener
Message Throttling √ √ (Container specific)
Messaging Support √ (Templates, Message Converters) -- (No support above the JMS API)
Distributed Transaction Support √ √
Remoting
Whereas messaging typically involves asynchronous messages being exchanged between systems, remoting is the ability to make synchronous requests between systems. This is useful in a variety of cases, for instance, in the context of my flight booking application, to perform authorization against a passenger's credit card. The most popular mechanisms for remoting today are Web services and RMI. Web services requests are typically transported via HTTP and as such are routable through corporate firewalls. Their message structure is XML and as such is not platform- or programming language- specific. RMI on the other hand is typically used for communication between Java applications and is generally not routable through firewalls.
Remoting―Functional Comparison
For this comparison consider the role the booking agent plays in coordinating the purchase of airline tickets (see Figure 2).
Figure 2. Remote Booking Agent. In this example the booking agent illustrated in the first article is distributed; clients interact with the agent unaware that it is running on a remote server.
The test for this use case is listed in the first article. It can be satisfied using Spring to implement a remote booking agent. In this implementation nothing must be done to the Spring booking agent to make it accessible remotely. All that must be done is to export the agent using the RmiServiceExporter as illustrated below:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter" lazy-init="false">
<property name="serviceName" value="bookingAgentService" />
<property name="service" ref="remoteBookingAgentService" />
<property name="serviceInterface" value="org.jug.flight.booking.BookingAgent" />
<property name="registryPort" value="1199" />
</bean>
<bean id="remoteBookingAgent" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:1199/bookingAgentService" />
<property name="serviceInterface" value="org.jug.flight.booking.BookingAgent" />
</bean>
To make the EJB 3.0 version of the booking agent remote all that must be done to it is to annotate it as a remote component as shown below:
@Stateful
@Remote
public class BookingAgentEJB implements BookingAgent
{
...
}
Notice that in both cases neither the interface nor logic of the booking agent had to change to expose the agent to remote callers. In fact, the Spring implementation did not require any changes to the booking agent at all! The same result could have been achieved with EJB 3.0 if I had decided to use XML instead of annotations to declare the component as remote.
Remoting―Non-Functional Comparison
EJB remoting is built on RMI and makes creating distributed Java applications easy, including support for security and transaction propagation. Stateless EJBs may also be exposed as Web services using the metadata annotations defined in JSR 181 (Web Services Metadata for the Java Platform).
Spring 2.0 supports various forms of remoting including RMI, Web services, Hessian, Burlap, and HTTP invokers (using JDK serialization). It is differentiated from EJB in that any POJO may be exposed as a remote object. This is implemented through a combination of Spring helper classes and configuration. But Spring remoting does not support security and transaction propagation as EJB remoting does.
Remoting―Summary
Table 2 summarizes the differences between remoting support in Spring and EJB 3.
Table 2. Remoting Support in Spring and EJB 3.0
Feature Spring EJB 3.0
Supported Protocols RMI, Web Services, Hessian, Burlap, HTTP RMI, Web Services
Expose remote objects √ (Any POJO) √ (EJBs only)
Instance pooling and load balancing -- √
Transaction Propagation -- √
Security Propagation -- √
Dependency Management
Dependency management refers to the ability to control the communication between components in a system. By externalizing (or "wiring") these interdependencies designers intend to create more loosely coupled systems. Systems with looser coupling are generally easier to extend, test, and maintain. Externalizing dependency management is also referred to as inversion of control (IOC) and the act of injecting dependencies into a class is referred to as dependency injection
There are three primary forms of dependency injection: setter, constructor, and field injection. For a discussion of setter versus constructor injection see "Inversion of Control Containers and the Dependency Injection Pattern" by Martin Fowler. The value of field injection is debatable. It is certainly convenient when using JEE's dependency injection annotations but it violates the principle of encapsulation and makes it more difficult to unit test classes.
Spring and EJB 3.0 both offer support for dependency management but implement different types of dependency injection. Spring supports both setter- and constructor-based injection. EJB 3.0 supports setter and field injection. Neither is a limiting decision, but this subtle difference should be kept in mind when considering Spring and EJB 3.0's support for dependency management.
One of the core features of the Spring framework is its IOC framework and so, not surprisingly, it offers much more mature and configurable support for dependency management then does EJB 3.0. In Spring dependencies are generally mapped (or "wired") via XML configuration files. The primary mechanism for wiring in JEE 5―and in EJB 3.0 specifically―is via source code annotations. Annotations have the advantage of being closer to the code they modify and thus can improve understandability of the system. On the other hand they create compile-time and runtime dependencies on their implementation and thus compromise the portability of annotated classes.
In Spring, applications dependencies are most typically configured via XML:
<bean id="bookingAgentSpring" class="org.jug.flight.booking.spring.BookingAgentSpring">
<property name="flightDAO" ref="flightDAO" />
<property name="ticketDAO" ref="ticketDAO" />
</bean>
By contrast, with JEE 5 and EJB 3.0, dependency injection often takes the form of annotations:
@Stateful
@Remote
public class BookingAgentEJB extends AbstractStatefulBookingAgent implements BookingAgent
{
@EJB
private FlightDAO flightDAO;
@EJB
private TicketDAO ticketDAO;
...
}
In larger applications with more interdependencies and resource requirements wiring can be an arduous task. The ability of the dependency injection container to implicitly wire dependencies is referred to as auto-wiring. Both Spring and EJB 3.0 have the ability to wire dependencies by both name and type.
A unique feature to Spring is its ability to invoke factory beans to create object instances. This allows object factories to be invoked to create object instances for injection. It can give developers programmatic control of the object creation process as well as legacy code to be adapted to the Spring dependency management mechanism.
The power of dependency management in the Spring framework is in its ability to manage any object, to integrate with legacy code bases, and to be highly extensible and configurable. The EJB 3.0 dependency management fac
1 Introduction
Software development professionals today are faced with a bewildering array of technology choices. It is not always easy to decide which technologies are most appropriate to employ in solving any particular business problem. In the area of enterprise Java development, there has recently been quite a bit of debate regarding the Spring framework and the Enterprise Java Bean 3.0 specification. It can be hard to find clear and honest advice through the cacophony of opinion.
Still, the question of the relative strengths and weaknesses of each is a question I encounter frequently in my consulting work. But while there is plenty of information on Spring and EJB 3.0 individually, there is very little objective information that compares the two. This article will examine both technologies in relationship to one another with the goal of helping you understand the similarities and differences between them.
Spring and EJB are not the same thing. Most importantly, Spring is an implementation while EJB 3.0 is a specification. But they do have some areas of overlap, for example both provide a mechanism to deliver middleware services to Java applications. Spring was developed as a reaction against EJB and this makes comparison between the two natural. Particularly now that a new version of EJB is available, it is a good time to reevaluate how well EJB 3.0 has addressed the shortcomings of previous versions. And, to the extent that is has done so successfully, what does Spring continue to offer above and beyond EJB?
It should also be noted that EJB specifically addresses the development of distributed components whereas this emphasis is missing from the Spring definition.
This article is the first in a two-part series; each article will look at different characteristics of Spring and EJB 3. The characteristics I chose are ones that are particularly meaningful within the context of enterprise application development. I will explore each characteristic using a flight booking example application, which has a rich domain model and illustrates well enterprise challenges such as concurrency, conversations, transactionality, messaging, and scheduling. I've also used unit tests to determine whether Spring and EJB 3.0 offer comparable functionality.
The Definition
Before launching into a more in-depth comparison it is helpful to consider for a moment what Spring and EJB each claim to be. The Spring Web site defines Spring as "a layered Java/J2EE application framework." And EJB claims to be "a component architecture for the development and deployment of object-oriented, distributed, enterprise-level applications" (J2EE glossary).
First, we can tell from these definitions that both intend to operate in the enterprise Java development space. But Spring claims to be an application framework whereas EJB an architecture. While what is meant by architecture varies greatly, a general definition could be the big decisions that are hard to change, or the structure of a software system. A framework implies support whereas an architecture implies decision. It may be helpful to keep these nuances in mind during the comparison. And it should also be noted that EJB specifically addresses the development of distributed components whereas this emphasis is missing from the Spring definition
2 Persistence
Persistence is a critical component of any enterprise application. And not surprisingly both Spring and EJB 3.0 provide robust support for it. True to its design philosophy, Spring doesn't re-implement a persistence framework but rather integrates with many popular ones including JDBC, Hibernate, JDO, iBatis, and (as of Spring 2.0) the Java Persistence API (JPA). With the EJB 3.0 specification entity beans have been replaced with the JPA.
JPA aims to provide a simplified, light-weight, object-relational mapping framework. This specification (a separate document within the EJB 3.0 specification) defines interfaces for interacting with the persistence provider and for mapping entities to a relational database.
Persistence―Functional Comparison
To illustrate the comparability in support between Spring and EJB 3.0 consider the domain model for the flight booking application shown in Figure 1.
Figure 1. Flight Booking Domain Model. The flight booking domains illustrated many interesting ORM concepts such as composition, bi-directionality, and ternary associations.
The unit test below verifies that a ticket can be created, associated with existing flights, seats assigned, and saved to a database:
public void testSave()
{
Ticket ticket = TicketMother.create();
Flight outboundFlight = flightDAO.findById(1);
Flight returnFlight = flightDAO.findById(2);
ticket.setOutboundFlight(outboundFlight);
ticket.setReturnFlight(returnFlight);
ticket.getPassenger().setFlightDetails(outboundFlight,
new PassengerFlightDetails(getSeatForFlight(outboundFlight, "2A")));
ticket.getPassenger().setFlightDetails(returnFlight,
new PassengerFlightDetails(getSeatForFlight(returnFlight, "2B")));
ticketDAO.save(ticket);
ticket = ticketDAO.findById(ticket.getId());
assertEquals("John", ticket.getPassenger().getFirstName());
assertEquals("2A", ticket.getPassenger().getDetailsForFlight(ticket.getOutboundFlight())
.getSeat().getNumber());
}
This test can be run successfully against both Spring and EJB 3.0 implementations indicating that the two technologies have functionally equivalent support for basic ORM persistence. The two implementations are also very similar. I have chosen to illustrate the Spring implementation with Hibernate since it is the most common ORM provider used in conjunction with Spring and because JPA support is not available in the current production release of Spring. A Spring/Hibernate implementation of the TicketDAO is class is illustrated below:
public class TicketSpringDAO extends HibernateDaoSupport implements TicketDAO{
public Ticket save(Ticket ticket){
getHibernateTemplate().saveOrUpdate(ticket);
return ticket;
}
…
}
And the corresponding EJB 3.0 implementation is seen here:
@Stateless
public class TicketEJBDAO implements TicketDAO{
@PersistenceContext(unitName = "flightdb")
private EntityManager em;
public Ticket save(Ticket ticket){
em.persist(ticket);
return ticket;
}
...
}
Obviously these implementations are both very similar. In the Spring implementation the details of working with Hibernate are abstracted away by the HibernateDaoSupport class, which provides the Hibernate plumbing and assists in working with the Spring Hibernate template and dependencies. In the EJB 3.0 implementation the entity manager is automatically injected by the container.
The Hibernate Session and the JPA Entity Manager are roughly equivalent but there are a couple of important differences to keep in mind. The Hibernate session is an entity cache as well an interface into the ORM engine. In JPA these two concepts are separated. The persistence context functions as the cache while the entity manager functions at the interface into the ORM engine.
It is also interesting to notice the different approaches towards mapping persistence entities to their relational equivalents. The Spring/Hibernate mapping is expressed in the following XML mapping file:
<hibernate-mapping package="org.jug.flight.domain" default-lazy="false">
<class name="Ticket">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="status" type="Status" />
<many-to-one name="outboundFlight" column="outbound_flight" />
<many-to-one name="returnFlight" column="return_flight" />
<many-to-one name="passenger" column="passenger_id" cascade="all" />
</class>
</hibernate-mapping>
JPA applications often choose to use annotations to express this mapping because it reduces the configuration required, keeps the mapping data close to the objects it modifies, and enhances visibility. This approach is illustrated below:
@Entity
public class Ticket implements Serializable{
@Id
@GeneratedValue
private long id;
private Status status;
private double price;
@ManyToOne(cascade = CascadeType.PERSIST)
private Passenger passenger;
private Flight outboundFlight;
private Flight returnFlight;
...
}
As you can see, Spring/Hibernate and EJB 3.0/JPA offer roughly functionally equivalent support for persistence via ORM.
Persistence―Non-Functional Comparison
The primary difference between Spring and EJB 3.0's support for persistence may at first appear to be compatibility with different persistence frameworks. Spring provides support for many different ORM implementations. In EJB, only JPA is explicitly supported. But it is important to remember that JPA is merely a specification and many different providers support JPA including Hibernate, Kodo, and TopLink. Table 1 lists the default JPA provider for several popular EJB 3.0 application servers:
Table 1. JPA Providers Supported by Leading App Servers.
Application Server JPA Implementation
JBoss Hibernate
BEA Kodo (OpenJPA)
Oracle TopLink
Glassfish TopLink
In this sense both technologies offer a choice in persistence providers. If you need to use provider-specific features that are not part of the JPA specification then you can certainly do so in your EJB application but you may sacrifice platform cross-compatibility.
Another important concept when working with ORM tools is caching. The cache is important when considering Spring and EJB 3.0 because in order for multiple components to collaborate on a single unit of work this cache must be shared between them. In Spring this is typically accomplished by applications attaching the session (and transaction) to the thread using thread local variables. Although Spring offers classes to make implementing this easier it still needs to be addressed by the development team. In EJB 3.0 by contrast the persistence context is propagated automatically on the transaction.
Object models are often rich and complex graphs. It would be inefficient if every time a persistent entity was retrieved from the database if all of its relationships were automatically fetched. To address this ORM providers often provide functionality such as lazy initialization, which defers the execution of particular database operations until the data is actually needed. This prevents a lot of unnecessary and expensive database calls but can complicate application logic quite a bit. If the cache associated with a lazily initialized collection is closed when the collection is accessed then exceptions are thrown.
The approach taken by Spring/Hibernate is to open and close the cache around view creation. Spring provides an OpenSessionInViewFilter (and interceptor) to simplify this, but it must be configured. The EJB 3.0 specification adds an interesting scope to its persistence context that allows the lifetime of the cache to be bound to the lifetime of a stateful session bean. This is named an extended persistence context and is specified by annotating the persistence context in the stateful session bean. This can simplify working with long application transactions and lazily loaded collections.
Persistence―Summary
Because JPA supports pluggable persistence providers and because Spring supports those same providers directly these two approaches are very comparable. Table 2 highlights the major comparison points.
Table 2. Summation of Persistence Comparison Between Spring and EJB 3.0.
Feature Spring EJB 3.0
Simple ORM Persistence √ √
Implementation Hibernate, JPA, JDO, TopLink, iBatis JPA (providers include Hibernate, Kodo and Toplink)
JDBC Support √ X
Mapping XML, Annotations Annotations, XML
Cache Propagation Thread local Transaction
Extended cache scoping Open session in view Extended persistence context
Standard √ (If using JPA) √
3 Transaction Management
Another critical factor in many enterprise applications is transaction management. This is because in high-volume applications many users may be attempting to access the same data simultaneously. And in single- and multi-user situations you may often want a particular operation to either entirely succeed or fail.
Transaction Management―Functional Comparison
To illustrate the functionality capabilities of Spring and EJB 3.0 to provide transactionality consider the case of purchasing a ticket in the flight booking scenario (see Figure 2).
Figure 2. Purchase Ticket Sequence Diagram. If any step in the purchase ticket sequence fails then the system should be left in a consistent state.
The unit test shown below verifies that the purchase ticket operation functions in a transactional manner:
public void testPurchaseTicket_DebitFailure() throws Exception
{
// Creates a pending ticket
Ticket ticket = createTicket();
// Store original ticket count from DB
int count = ticketDAO.findAll().size();
// Setup a credit authorizer which will throw an exception on debit
setupMockAuthorizer(true);
try
{
bookingAgent.purchaseTicket(ticket, true);
fail("System failure was not thrown as was intended");
}
catch (InsufficientFundsException e)
{
// Correct behavior since we're testing transactional semantics
}
// Verify transaction rolled-back
assertEquals(count, ticketDAO.findAll().size());
}
This test can also be successfully executed against both Spring and EJB 3.0 implementations. The Spring implementation is shown below:
public Ticket purchaseTicket(Ticket ticket)
{
creditAuthorizer.authorize(ticket.getPrice(), null);
ticket.setStatus(Status.PURCHASED);
ticketDAO.save(ticket);
creditAuthorizer.debit(ticket.getPrice());
return ticket;
}
Compare this to the EJB 3.0 implementation:
public Ticket purchaseTicket(Ticket ticket)
{
creditAuthorizer.authorize(ticket.getPrice(), null);
ticket.setStatus(Status.PURCHASED);
ticketDAO.save(ticket);
creditAuthorizer.debit(ticket.getPrice());
return ticket;
}
Notice that they are exactly the same and that neither has to explicitly deal with transaction management! This illustrates well the value of declarative programming. Developers can write code that focuses on the business problem, and cross-cutting concerns such as transactionality can be modularized and applied across an application. In this case both Spring and EJB 3.0 offer comparable functionality in dealing with transactions and both succeed modularizing this functionality in a way that it doesn't clutter business logic.
Transaction Management―Non-Functional Comparison
Import aspects of transaction management to consider include demarcation, propagation, and isolation. The process of bounding a unit of work with transactional semantics is termed demarcation. And if multiple components are collaborating on a single unit of work, this transaction should be shared (propagated) between them. Lastly, isolation levels allow developers to tune the degree of isolation that the transaction has from other transactions.
In Spring, transaction demarcation, propagation, and isolation are all declared through its AOP facilities. Transactional proxies can be explicitly defined in its XML configuration files or can be applied using aspect semantics. The approach of wiring in a transactional proxy is illustrated below:
<bean id="bookingAgent" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="bookingAgentSpring" />
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="bookingAgentSpring" class="org.jug.flight.booking.spring.BookingAgentSpring">
<property name="flightDAO" ref="flightDAO" />
<property name="ticketDAO" ref="ticketDAO" />
<property name="screener" ref="screener" />
</bean>
EJB 3.0 on the other hand applies transactional semantics automatically on all public methods of session beans so no special configuration is required. Developers can tune the transaction propagation levels via annotations or XML:
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Ticket purchaseTicket(Ticket ticket)
{
...
}
The EJB 3.0 specification declares transaction isolation to be resource-manager specific, thus there is no standard way to express this requirement in EJB 3.0.
Spring offers integration with many different types of transaction APIs including JDBC, Hibernate, and JTA. EJB 3.0 only supports JTA transactions. The choice of transaction API is important in two primary cases. First, not all containers (such as Tomcat) support JTA, although all EJB containers do. Second, in order to join multiple resources (such as JDBC and JMS) in a single distributed transaction, a JTA implementation is required. If you require JTA and are running in an environment that does not provide a JTA implementation you can consider open-source implementations of JTA such as the Java Open Transaction Manager (JOTM).
Transaction Management―Summary
Both Spring and EJB 3.0 offer an integrated approach to transaction management that is compatible with their persistence mechanisms (see Table 3).
Table 3. Equivalent Transaction Management Functionality in Both Spring and EJB 3.0.
Feature Spring EJB 3.0
Declarative Transactions √ √
Programmatic Transactions √ √
Demarcation AOP Session bean methods
Supported Transaction Types JDBC, Hibernate, JTA JTA
Support for Distributed Transactions √ (With JTA) √
Configuration XML Transactional by default, override with annotations or XML
Standard √ (With JTA) √
4 Statefulness
One of the more characteristic differences between Spring and EJB 3.0 is their respective approaches to managing state. State is an important element to many applications because a user's interaction with the system typically involves a series of steps with cumulative meaning. For example, in the context of the example flight booking application, while booking a ticket a customer navigates between browsing for flights, selecting seat assignments, and providing payment information. As the user navigates between these steps the choices made in previous steps are implicit in the context of the conversation
Recognizing the important role state plays in many applications, EJB provides a first level construct, the Stateful Session Bean (SFSB), dedicated to managing a stateful conversation. With SFSBs subsequent invocations of a bean share the bean instance and thus the same state. SFSBs are also designed with issues of scalability and fail-over in mind although the implementation of many of these concerns are left to specific vendor implementations. Spring does not offer any direct equivalent to SFSBs although several approaches exist for accomplishing the same result.
Statefulness―Functional Comparison
To illustrate the approaches toward state offered by Spring and EJB 3.0 consider the sequence of steps involved in booking a ticket (see Figure 3).
Figure 3. Book Ticket Sequence Diagram. Passengers book tickets through a series of interactions with a booking agent. These steps are conversational and the context of the conversation (the state) evolves with each step.
The following unit test verifies that the booking agent can be interacted with in a stateful manner:
public void testPurchaseTicket_UseCase() throws Exception
{
// Begin purchase ticket conversation
FlightCriteria flightCriteria = new FlightCriteria("DFW", "AUS", DateUtilities
.parseDate("2006-01-15"));
bookingAgent.bookRoundTripTicket(flightCriteria);
// Select outbound flight
List outboundFlights = bookingAgent.listOutboundFlights();
assertEquals(3, outboundFlights.size());
bookingAgent.selectOutboundFlight(selectFlight(outboundFlights, 1));
// Select return flight
List<Flight> returnFlights = bookingAgent.listReturnFlights();
assertEquals(3, returnFlights.size());
bookingAgent.selectReturnFlight(selectFlight(returnFlights, 2));
// Select passenger
Passenger passenger = new Passenger("Rod", "Coffin");
bookingAgent.selectPassenger(passenger);
// Select seats
bookingAgent.selectOutboundSeat(selectSeat(bookingAgent.retrieveCurrentTicket()
.getOutboundFlight(), "1B"));
bookingAgent.selectReturnSeat(selectSeat(bookingAgent.retrieveCurrentTicket()
.getReturnFlight(), "1A"));
// Purchase ticket
Ticket ticket = bookingAgent.purchaseTicket();
assertTrue(ticket.getId() != 0);
}
Both Spring and EJB 3.0 can satisfy this unit test. The Spring implementation is shown below:
public class BookingAgentSpring implements BookingAgent
{
private FlightCriteria outboundCriteria;
private FlightCriteria returnCriteria;
private Ticket ticket;
public Ticket bookRoundTripTicket(FlightCriteria flightCriteria)
{
outboundCriteria = flightCriteria;
returnCriteria = reverseCriteria(flightCriteria);
ticket = new Ticket();
ticketDAO.save(ticket);
return ticket;
}
...
}
In order for Spring-managed beans to contain state, instances cannot be shared between callers. To accomplish this, the scope of stateful beans in Spring should be configured as prototype. This means that each time a bean is retrieved from a bean factory a new instance is created.
<bean id="bookingAgentSpring" scope=”prototype”
class="org.jug.flight.booking.spring.BookingAgentSpring">
...
</bean>
Compare with the EJB 3.0 implementation:
@Stateful
public class BookingAgentEJB implements BookingAgent
{
private FlightCriteria outboundCriteria;
private FlightCriteria returnCriteria;
private Ticket ticket;
public Ticket bookRoundTripTicket(FlightCriteria flightCriteria)
{
outboundCriteria = flightCriteria;
returnCriteria = reverseCriteria(flightCriteria);
ticket = new Ticket();
ticketDAO.save(ticket);
return ticket;
}
...
}
In both cases the booking agent can store state and participate with the caller in a conversational manner. So it is possible to create stateful behavior with both Spring and EJB 3.0 but the approaches and implications of these approaches differ significantly with one another.
Statefulness - Non-Functional Comparison
The examples above illustrate how stateful objects could be implemented in both Spring and EJB 3.0. Although functionally equivalent there is a significant difference in how state is managed. In the Spring implementation the caller stores the complete booking agent (see Figure 4), whereas in the EJB 3.0 implementation the caller is merely storing a proxy to the booking agent (see Figure 5). Because the caller in the EJB 3.0 version is only holding onto a proxy, the SFSB and its associated state exists within―and is managed by―the server.
Figure 4. Client Managed State. One approach to implementing stateful beans with Spring is to store the stateful component in the HttpSession managed by the web container.
Figure 5. EJB Managed State. Clients interact with EJB components via proxies. This allows the component's state to be managed by the EJB container.
There are several advantages to the EJB approach with SFSBs. First, SFSB are designed to optimize server performance by allowing SFSBs to be swapped between memory and persistence storage. This allows application servers to balance resources to achieve optimal performance. Secondly, SFSBs can be run in a separate process from the caller. This might be performed to allow tiers of an application to be scaled independently or for security purposes. Thirdly, SFSBs may also implement the optional SessionSynchronization interface to in order to rollback bean state in response to a transaction rollback event.
One of the major criticisms typically heaped on SFSBs is that they do not perform or scale as well as stateless architectures. In response to this, the Spring framework is primarily a stateless framework. But given that state is an important part of many applications, there has to some options for expressing state. Typical approaches include putting state in the database, the HTTP Session, or in an in-memory cache. In the example above, the stateful Spring booking agent could be stored in either of these vehicles. But each of these options has its own disadvantages, as shown in Table 4.
Table 4. Alternatives to Stateful Session Beans in Spring.
SFSB Alternative Advantages Disadvantages
Database Shared by all application servers. Usually the least scalable tier of an application. If using a second-level cache then data must be replicated.
HTTP Session Simple to use. Data must be replicated and is not transaction aware.
Memory Cache Shared by all application servers, potentially more efficient then using the database. Data must be replicated and is not transaction aware.
The scalability argument applied against SFSBs can also be applied to each of these three alternatives to SFSBs (as shown in Table 4). In addition you must spend considerable development effort re-implementing functionality already provided by SFSBs. These tradeoffs should be considered seriously when deciding upon your application's approach to state.
Both Spring and EJB 3 offer hooks that allow developers to tie into the lifecycle of managed beans. Spring provides the InitializingBean and DisposableBean interfaces, which provide hooks to notify beans after they have been initialized by Spring and before destruction. Similarly, EJB components can optionally receive notifications after creation, before destruction, before passivation, and after activation.
Lastly, the issue of how to synchronize the conversational state of a bean with the status of a transaction is one that must be resolved, whether using SFSBs or an alternative. By default, when a transaction rolls back, all transactional resources participating in the transaction (such as database and messaging systems) roll back as well. But the state of the beans involved will not be rolled back. If these beans are stateful then they will be out of sync with the persistence data store. EJB 3.0 SFSBs using container-managed transactions can implement the SessionSynchronization interface to be notified of transactional events. Spring does not offer equivalent functionality but it could be handled in some custom way by the application.
Statefulness―Summary
Both Spring and EJB 3.0 enable stateful applications. In an EJB 3.0 application the primary mechanism for accomplishing this is the use of SFSBs. Spring is primarily a stateless architecture but can facilitate statefulness through prototype beans. Applications using this approach need to consider its implications for performance and scalability and will need some other method of addressing the issues handled by SFSBs.
Table 5. Differences in State-handling Between Spring and EJB 3.0.
Feature Spring EJB 3.0
Stateful construct Prototype (instance) beans Stateful Session Bean
Instance Management Singleton, prototype, request, session, global session Instance per lookup
Lifecycle Management √ (Initialization/Destruction) √ (Creation/Destruction, Activation/Passivation)
Transaction awareness X √ (SessionSynchronization interface)
Standard N/A √
Messaging
Enterprise applications often don't exist in isolation but must interact with other applications and services running within and outside a company. Messaging is a common approach to this problem and support is provided by both Spring and EJB 3.0. There are two basic operations in messaging: sending and receiving. And, typically, there are two methods of receiving messages: explicit message retrieval and automatic notification (listening).
Messaging―Functional Comparison
To explore how Spring and EJB 3.0 each implement the sending of Java Message Service (JMS) messages, consider the sequence of steps I've outlined for the purchasing of an airline ticket (see Figure 1).
A Spring implementation of the TsaScreener object is shown below:
public class TsaPassengerScreenerClientSpring implements TsaPassengerScreener
{
private JmsTemplate jmsTemplate;
private Queue queue;
public void screenPassenger(final Passenger passenger)
{
jmsTemplate.send(queue, new MessageCreator()
{
public Message createMessage(Session session) throws JMSException
{
return session.createObjectMessage(passenger);
}
});
} ...
}
The necessary configuration to support this implementation is shown below:
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate" />
<bean id="tsaQueue" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate">
<ref bean="jndiTemplate" />
</property>
<property name="jndiName">
<value>java:comp/env/queue/tsaQueue</value>
</property>
</bean>
<bean id="tsaQCF" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate">
<ref bean="jndiTemplate" />
</property>
<property name="jndiName">
<value>java:comp/env/queue/tsaQCF</value>
</property>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<constructor-arg>
<ref bean="tsaQCF" />
</constructor-arg>
</bean>
<bean id="tsaPassengerScreener"
class="org.jug.flight.booking.spring.TsaPassengerScreenerClientSpring">
<property name="jmsTemplate" ref="jmsTemplate" />
<property name="tsaQueue" ref="tsaQCF" />
</bean>
Compare this to the EJB 3.0 implementation:
@Stateless
public class TsaPassengerScreenerClientEJB implements TsaPassengerScreener
{
@Resource(mappedName = "java:/ConnectionFactory")
QueueConnectionFactory factory;
@Resource(mappedName = "queue/tsaQueue")
Queue queue;
public void screenPassenger(Passenger passenger)
{
try
{
QueueConnection con = factory.createQueueConnection();
QueueSession session = con.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
ObjectMessage msg = session.createObjectMessage(passenger);
QueueSender sender = session.createSender(queue);
sender.send(msg);
session.close();
con.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Figure 1. Purchase Ticket Sequence Diagram. In this example the purchase ticket use case has been expanded to send a message to the TSA with the details of the passenger who is booking a flight.
Notice that the Spring implementation uses the JmsTemplate helper class to simplify working with the JMS API to send the message. Also, notice that in both cases the container injects the JMS resources (queue connection factory and queue) into the screener object. I'll explore injection more later, in the section on dependency management. But at this point it is interesting to note the Spring configuration is more verbose then the EJB equivalent.
After the passenger details message has been sent the TSA needs to receive and process it. Both Spring and EJB 3.0 can support notification of objects when incoming messages arrive. These objects are referred to as message listeners. A Spring implementation of a message-driven POJO (MDP) is shown below:
public class TsaPassengerScreenerMDP implements MessageListener
{
public void onMessage(Message message)
{
ObjectMessage objectMessage = (ObjectMessage) message;
try
{
Passenger passenger = (Passenger) objectMessage.getObject();
// Process message
}
catch (JMSException e)
{
e.printStackTrace();
}
}
}
The necessary configuration to support this implementation is shown below:
<bean id="tsaListener" class="org.jug.flight.booking.spring.TsaPassengerScreenerMDP" />
<bean id="listenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="concurrentConsumers" value="5" />
<property name="connectionFactory" ref="tsaQCF" />
<property name="destination" ref="tsaQueue" />
<property name="messageListener" ref="tsaListener" />
</bean>
Compare this to the EJB 3.0 implementation of a message-driven bean (MDB):
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/tsaQueue") })
public class TsaPassengerScreenerMDB implements MessageListener
{
public void onMessage(Message message)
{
ObjectMessage objectMessage = (ObjectMessage) message;
try
{
Passenger passenger = (Passenger) objectMessage.getObject();
// Process message
}
catch (JMSException e)
{
e.printStackTrace();
}
}
}
As shown above, both Spring and EJB 3.0 enable the sending and receiving of JMS messages. And interestingly the implementations of message consumption via a Spring MDP and an EJB MDB are quite similar. Additionally, both solutions allow the volume of concurrent message processing to be throttled allowing you to tune the amount of resources allocated within an application to message processing. In the case of EJBs this is a container-specific function whereas with Spring it is a function of the message listener container.
Another important feature of enterprise messaging solutions is their ability to participate in distributed transactions. Often multiple resources will be involved in message processing (typically messaging and database resources) and the work performed on these resources should be performed atomically. Both Spring and EJB 3.0 allow messaging activities to participate in distributed transactions. In EJB 3.0 however, in order to have message acknowledgement tied to successful completion of a transaction, you will need to use container-managed transactions, otherwise messages that have been previously processed may be redelivered. The same can be accomplished in Spring when using JTA transactions and when the message container is defined as "sessionTransacted." In either case you could choose to allow message redelivery and handle this case programmatically.
A more telling difference in messaging support between Spring and EJB is the type of objects that can be configured to listen for incoming messages. In EJB message listeners must be EJB components whereas in Spring any POJO can potentially be activated in response to incoming messages. Both require that the message listener implement an interface and both support the javax.jms.MessageListener interface for JMS message listeners. However, since the 2.1 version of the EJB specification EJB MDBs can support other messaging interfaces such as javax.resource.cci.MessageListener which allow MDBs to be activated by a JCA CCI connector.
Messaging―Non-Functional Comparison
Most Java applications use JMS to interact with an underlying message provider. This is the case with both Spring and EJB 3.0. But Spring provides templates that assist with retrieving the necessary message resources from the application server (via JNDI) and for creating, sending, and receiving messages. EJB 3.0 allows message resources to be injected into a class via its dependency injection facilities, but it does not provide any facility equivalent to Spring's template to simplify the use of the JMS API. Spring also provides message converters which can automate the conversion of Java objects to message content.
In terms of standardization both approaches support the Java standard for messaging, JMS. In terms of implementation however MDBs are a JCP standard whereas Spring MDPs are not. But although MDPs are not a standard they are portable as the Spring runtime can run in any JSE or JEE container.
Messaging―Summary
Table 1 summarizes the key differences between Spring and EJB 3.0’s messaging support.
Table 1. Messaging Features in Spring and EJB 3.0.
Feature Spring EJB 3.0
Message Listening √ √
Listener Types Any POJOimplementing the javax.jms.MessageListener interface MDBs implementing any interface appropriate to its messaging type, usually javax.jms.MessageListener
Message Throttling √ √ (Container specific)
Messaging Support √ (Templates, Message Converters) -- (No support above the JMS API)
Distributed Transaction Support √ √
Remoting
Whereas messaging typically involves asynchronous messages being exchanged between systems, remoting is the ability to make synchronous requests between systems. This is useful in a variety of cases, for instance, in the context of my flight booking application, to perform authorization against a passenger's credit card. The most popular mechanisms for remoting today are Web services and RMI. Web services requests are typically transported via HTTP and as such are routable through corporate firewalls. Their message structure is XML and as such is not platform- or programming language- specific. RMI on the other hand is typically used for communication between Java applications and is generally not routable through firewalls.
Remoting―Functional Comparison
For this comparison consider the role the booking agent plays in coordinating the purchase of airline tickets (see Figure 2).
Figure 2. Remote Booking Agent. In this example the booking agent illustrated in the first article is distributed; clients interact with the agent unaware that it is running on a remote server.
The test for this use case is listed in the first article. It can be satisfied using Spring to implement a remote booking agent. In this implementation nothing must be done to the Spring booking agent to make it accessible remotely. All that must be done is to export the agent using the RmiServiceExporter as illustrated below:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter" lazy-init="false">
<property name="serviceName" value="bookingAgentService" />
<property name="service" ref="remoteBookingAgentService" />
<property name="serviceInterface" value="org.jug.flight.booking.BookingAgent" />
<property name="registryPort" value="1199" />
</bean>
<bean id="remoteBookingAgent" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:1199/bookingAgentService" />
<property name="serviceInterface" value="org.jug.flight.booking.BookingAgent" />
</bean>
To make the EJB 3.0 version of the booking agent remote all that must be done to it is to annotate it as a remote component as shown below:
@Stateful
@Remote
public class BookingAgentEJB implements BookingAgent
{
...
}
Notice that in both cases neither the interface nor logic of the booking agent had to change to expose the agent to remote callers. In fact, the Spring implementation did not require any changes to the booking agent at all! The same result could have been achieved with EJB 3.0 if I had decided to use XML instead of annotations to declare the component as remote.
Remoting―Non-Functional Comparison
EJB remoting is built on RMI and makes creating distributed Java applications easy, including support for security and transaction propagation. Stateless EJBs may also be exposed as Web services using the metadata annotations defined in JSR 181 (Web Services Metadata for the Java Platform).
Spring 2.0 supports various forms of remoting including RMI, Web services, Hessian, Burlap, and HTTP invokers (using JDK serialization). It is differentiated from EJB in that any POJO may be exposed as a remote object. This is implemented through a combination of Spring helper classes and configuration. But Spring remoting does not support security and transaction propagation as EJB remoting does.
Remoting―Summary
Table 2 summarizes the differences between remoting support in Spring and EJB 3.
Table 2. Remoting Support in Spring and EJB 3.0
Feature Spring EJB 3.0
Supported Protocols RMI, Web Services, Hessian, Burlap, HTTP RMI, Web Services
Expose remote objects √ (Any POJO) √ (EJBs only)
Instance pooling and load balancing -- √
Transaction Propagation -- √
Security Propagation -- √
Dependency Management
Dependency management refers to the ability to control the communication between components in a system. By externalizing (or "wiring") these interdependencies designers intend to create more loosely coupled systems. Systems with looser coupling are generally easier to extend, test, and maintain. Externalizing dependency management is also referred to as inversion of control (IOC) and the act of injecting dependencies into a class is referred to as dependency injection
There are three primary forms of dependency injection: setter, constructor, and field injection. For a discussion of setter versus constructor injection see "Inversion of Control Containers and the Dependency Injection Pattern" by Martin Fowler. The value of field injection is debatable. It is certainly convenient when using JEE's dependency injection annotations but it violates the principle of encapsulation and makes it more difficult to unit test classes.
Spring and EJB 3.0 both offer support for dependency management but implement different types of dependency injection. Spring supports both setter- and constructor-based injection. EJB 3.0 supports setter and field injection. Neither is a limiting decision, but this subtle difference should be kept in mind when considering Spring and EJB 3.0's support for dependency management.
One of the core features of the Spring framework is its IOC framework and so, not surprisingly, it offers much more mature and configurable support for dependency management then does EJB 3.0. In Spring dependencies are generally mapped (or "wired") via XML configuration files. The primary mechanism for wiring in JEE 5―and in EJB 3.0 specifically―is via source code annotations. Annotations have the advantage of being closer to the code they modify and thus can improve understandability of the system. On the other hand they create compile-time and runtime dependencies on their implementation and thus compromise the portability of annotated classes.
In Spring, applications dependencies are most typically configured via XML:
<bean id="bookingAgentSpring" class="org.jug.flight.booking.spring.BookingAgentSpring">
<property name="flightDAO" ref="flightDAO" />
<property name="ticketDAO" ref="ticketDAO" />
</bean>
By contrast, with JEE 5 and EJB 3.0, dependency injection often takes the form of annotations:
@Stateful
@Remote
public class BookingAgentEJB extends AbstractStatefulBookingAgent implements BookingAgent
{
@EJB
private FlightDAO flightDAO;
@EJB
private TicketDAO ticketDAO;
...
}
In larger applications with more interdependencies and resource requirements wiring can be an arduous task. The ability of the dependency injection container to implicitly wire dependencies is referred to as auto-wiring. Both Spring and EJB 3.0 have the ability to wire dependencies by both name and type.
A unique feature to Spring is its ability to invoke factory beans to create object instances. This allows object factories to be invoked to create object instances for injection. It can give developers programmatic control of the object creation process as well as legacy code to be adapted to the Spring dependency management mechanism.
The power of dependency management in the Spring framework is in its ability to manage any object, to integrate with legacy code bases, and to be highly extensible and configurable. The EJB 3.0 dependency management fac
相關文章
- Nginx VS Traefik ComparisonNginx
- trait in rust, and comparison with interface in javaAIRustJava
- However small the rewards may seem in comparison
- zero-shot-learning-definition-examples-comparison
- In-depth analysis of the comparison between AT and XA of distributed transactions
- rust-quiz:011-function-pointer-comparison.rsRustUIFunction
- PaperRead - Comparison of Fundamental Mesh Smoothing Algorithms for Medical Surface ModelsGo
- ISO C++ forbids comparison between pointer and integer [-fpermissive]C++ORB
- Madden NFL 22's Launch Should be a Touchdown In comparison to NFL 21
- Mybatis實現條件IN查詢(foreach)和invalid comparison異常MyBatis
- 記錄線上APP一個排序比較引發的崩潰 Comparison method violates its general contract!APP排序
- spring、spring MVC、spring BootMVCSpring Boot
- Spring系列之初識Spring Spring概述Spring
- Spring框架 - Spring和Spring框架組成Spring框架
- Spring Boot —— Spring SecuritySpring Boot
- Spring Boot整合Spring BatchSpring BootBAT
- Spring Boot整合Spring AopSpring Boot
- Spring Boot整合Spring SecuritySpring Boot
- SPRING-spring註解整理Spring
- Spring---Spring專題(二)Spring
- Spring Boot:Spring Boot配置SwaggerSpring BootSwagger
- Spring Boot:Spring Boot配置MybatisSpring BootMyBatis
- 理解Spring(一):Spring 與 IoCSpring
- spring:spring與mybatis的整合SpringMyBatis
- Spring Boot:整合Spring Data JPASpring Boot
- Spring Cloud(二):Spring Cloud ConfigSpringCloud
- 「Spring認證」Spring 框架概述Spring框架
- Spring框架系列(2) - Spring簡單例子引入Spring要點Spring框架單例
- SpringSpring
- Spring Boot整合Spring Cloud Netflix元件Spring BootCloud元件
- 【Spring系列】- Spring迴圈依賴Spring
- spring學習:spring原始碼_BeanDefinitionSpring原始碼Bean
- 【Spring】Spring後置處理器Spring
- spring-boot 整合 spring-securitySpringboot
- spring-boot 整合 spring-sessionSpringbootSession
- 如何從Spring Security 5遷移到Spring Security 6/Spring Boot 3Spring Boot
- Spring security(四)-spring boot +spring security簡訊認證+redis整合Spring BootRedis
- 非spring boot (即spring) 使用/整合 Spring cloud Config 分散式配置中心Spring BootCloud分散式
- Spring原始碼分析——搭建spring原始碼Spring原始碼