此内容没有您所选择的语言版本。
1.6.3. Understanding Seam conversations
This tutorial concentrates upon one particular piece of functionality: placing a hotel reservation. From the user's perspective, hotel search, selection, booking, and confirmation are one continuous unit of work — a conversation. However, from our perspective, it is important that searching remains separate so that users can select multiple hotels from the same search results page, and open distinct conversations in separate browser tabs.
Most web application architectures do not have first class constructs to represent conversations, which makes managing conversational state problematic. Java web applications generally use a combination of several techniques. Some state is transferred in the URL, but what cannot be transferred here is either added to the
HttpSession
or recorded to the database at the beginning and end of each request.
Since the database is the least-scalable tier, this drastically reduces scalability. The extra traffic to and from the database also increases latency. In order to reduce redundant traffic, Java applications often introduce a data cache to store commonly-accessed data between requests. However, since invalidation is based upon an LRU policy, rather than whether the user has finished using the data, this cache is inefficient. It is also shared between concurrent transactions, which introduces further issues associated with keeping the cached state consistent with that of the database.
State held in the
HttpSession
suffers similar issues. The HttpSession
is fine for storing true session data — data common to all requests between user and application — but for data related to individual request series, it does not work so well. Conversations stored here quickly break down when dealing with multiple windows or the back button. Without careful programming, data in the HttpSession
can also grow quite large, which makes the session difficult to cluster. Developing mechanisms to deal with the problems these methods present (by isolating session state associated with distinct concurrent conversations, and incorporating failsafes to ensure conversation state is destroyed when a conversation is aborted) can be complicated.
Seam greatly improves conditions by introducing conversation context as a first class construct. Conversation state is stored safely in this context, with a well-defined life cycle. Even better, there is no need to push data continually between the application server and the database; the conversation context is a natural cache for currently-used data.
In the following application, the conversation context is used to store stateful session beans. These are sometimes regarded as detrimental to scalability, and in the past, they may have been. However, modern application servers have sophisticated mechanisms for stateful session bean replication. JBoss Enterprise Application Platform performs fine-grained replication, replicating only altered bean attribute values. Used correctly, stateful session beans pose no scalability problems, but for those uncomfortable or unfamiliar with the use of stateful session beans, Seam also allows the use of POJOs.
The booking example shows one way that stateful components with different scopes can collaborate to achieve complex behaviors. The main page of the booking application allows the user to search for hotels. Search results are stored in the Seam session scope. When the user navigate to a hotel, a conversation begins, and a conversation scoped component retrieves the selected hotel from the session scoped component.
The booking example also demonstrates the use of RichFaces Ajax to implement rich client behavior without handwritten JavaScript.
The search function is implemented with a session-scoped stateful session bean, similar to the one used in the message list example.
Example 1.28. HotelSearchingAction.java
The EJB standard @Stateful annotation identifies this class as a stateful session bean. Stateful session beans are scoped to the conversation context by default.
| |
The @Restrict annotation applies a security restriction to the component. It restricts access to the component allowing only logged-in users. The security chapter explains more about security in Seam.
| |
The @DataModel annotation exposes a List as a JSF ListDataModel . This makes it easy to implement clickable lists for search screens. In this case, the list of hotels is exposed to the page as a ListDataModel in the conversation variable named hotels .
| |
The EJB standard @Remove annotation specifies that a stateful session bean should be removed and its state destroyed after invocation of the annotated method. In Seam, all stateful session beans must define a parameterless method marked @Remove . This method will be called when Seam destroys the session context.
|
The main page of the application is a Facelets page. The fragment that relates to searching for hotels is shown below:
Example 1.29. main.xhtml
The RichFaces Ajax <a:support> tag allows a JSF action event listener to be called by asynchronous XMLHttpRequest when a JavaScript event like onkeyup occurs. Even better, the reRender attribute lets us render a fragment of the JSF page and perform a partial page update when the asynchronous response is received.
| |
The RichFaces Ajax <a:status> tag lets us display an animated image while we wait for asynchronous requests to return.
| |
The RichFaces Ajax <a:outputPanel> tag defines a region of the page which can be re-rendered by an asynchronous request.
| |
The Seam <s:link> tag lets us attach a JSF action listener to an ordinary (non-JavaScript) HTML link. The advantage of this over the standard JSF <h:commandLink> is that it preserves the operation of "open in new window" and "open in new tab". Also notice that we use a method binding with a parameter: #{hotelBooking.selectHotel(hot)} . This is not possible in the standard Unified EL, but Seam provides an extension to the EL that lets you use parameters on any method binding expression.
If you are wondering how navigation occurs, you can find all the rules in WEB-INF/pages.xml ; this is discussed in Section 7.7, “Navigation”.
|
This page displays search results dynamically as the user types, and passes a selected hotel to the
selectHotel()
method of HotelBookingAction
, where the real work occurs.
The following code shows how the booking example application uses a conversation-scoped stateful session bean to achieve a natural cache of persistent data related to the conversation. Think of the code as a list of scripted actions that implement the various steps of the conversation.
Example 1.30. HotelBookingAction.java
This bean uses an EJB3 extended persistence context, so that any entity instances remain managed for the whole life cycle of the stateful session bean.
| |
The @Out annotation declares that an attribute value is outjected to a context variable after method invocations. In this case, the context variable named hotel will be set to the value of the hotel instance variable after every action listener invocation completes.
| |
The @Begin annotation specifies that the annotated method begins a long-running conversation, so the current conversation context will not be destroyed at the end of the request. Instead, it will be reassociated with every request from the current window, and destroyed either by timeout due to conversation inactivity or invocation of a matching @End method.
| |
The @End annotation specifies that the annotated method ends the current long-running conversation, so the current conversation context will be destroyed at the end of the request.
| |
This EJB remove method will be called when Seam destroys the conversation context. Do not forget to define this method!
|
HotelBookingAction
contains all the action listener methods that implement selection, booking and booking confirmation, and holds state related to this work in its instance variables. This code is much cleaner and simpler than getting and setting HttpSession
attributes.
Even better, a user can have multiple isolated conversations per log in session. Log in, run a search, and navigate to different hotel pages in multiple browser tabs. You'll be able to work on creating two different hotel reservations at the same time. If you leave any one conversation inactive for long enough, Seam will eventually time out that conversation and destroy its state. If, after ending a conversation, you backtrack to a page of that conversation and try to perform an action, Seam will detect that the conversation was already ended, and redirect you to the search page.