As the sample application uses EJBs with local interfaces, there is no need to use the Business Delegate pattern, which requires more work to implement than a service locator.. The remot
Trang 1The AbstractLocalStatelessSessionServiceLocator subclass implements the located( ) template method to check that that the object is actually an EJB local home, and to invoke an abstract setEjbHome( ) method that must be implemented by subclasses that know the application-specific type
of the EJB home, enabling them to obtain EJB references from it:
Trang 2Infrastructure and Application Implementation
It's always safe to cache a reference to a local EJB home interface Although the EJB specification (§6.2.1) implies that it's safe to cache a remote home, there is a slight chance that a cached remote home reference may become "stale" in some servers (for example, if the remote server hosting the EJB was restarted during the client's lifetime)
The following UML class diagram illustrates the superclasses for both local and remote SLSB service locators, and how a LocalSLSBBoxOf f iceFactory class might be used in the sample application to create BoxOffice objects (actually EJBs) as required:
Let's look at how these superclasses can be used to simplify the implementation of a real service locator The BoxOff iceFactory interface is the public interface of our application-specific typed service locator:
public interface BoxOfficeFactory {
BoxOffice getBoxOffice() throws FatalException;
}
Extending the
com.interface21.ejb.access.AbstractionLocalStatelessSessionServiceLocator
superclass makes it trivial to implement this interface:
public class LocalSLSBBoxOfflceFactory
extends AbstractLocalStatelessSessionServiceLocator
implements BoxOf ficeFactory {
The implementation of the protected abstraction setEjbHome() method caches the home
interface, after casting it to the appropriate type This method will be invoked once, when the service locator is initialized:
Private BoxOfficeHomehome;
protected void setEjbHome(EJBLocalHome home)
{ this.home = (BoxOfficeHome) home; }
Trang 3The implementation of the getBoxOffice( ) method from the BoxOfficeFactory interface, which will
be invoked by code using the EJB, creates a new SLSB reference as required EJBCreate exceptions are caught and an unchecked exception, FatalException, is rethrown:
The following bean definition shows how such a service locator could be defined in the sample
application's bean factory The only property that must be set is jndiName, inherited from the
to hold on to a reference to the business interface
Transparent Dynamic Proxy: An Alternative to the Typed Service Locator for Local EJB Access
An alternative to the Service Locator pattern that achieves the same end is to conceal the factory in a custom bean definition, as described when we discussed our "bean factory" approach above The custom bean definition looks up and caches the bean's home interface before returning an object of the required interface, which is actually a dynamic proxy wrapping a new instance of the SLSB Before each method invocation, the dynamic proxy - which is threadsafe - transparently creates a new instance of the SLSB Although the custom bean definition doesn't know the type of the EJB, it knows that an SLSB local interface must expose a create ( ) method without arguments, which it can use a BeanWrapper object to invoke by name Benchmarks show that this approach has only a small performance overhead
This use of dynamic proxies and reflective method invocation sounds complex, but the complexity is hidden within framework code; it enables us to write less application code Applications need only define
a bean using the custom bean definition, as follows Note the specification of theJNDI name and business interface:
Trang 4Infrastructure and Application Implementation
<customDefinition property="jndiName">
ejb/BoxOffice
</customDefinition>
</bean>
That's it: calling code can simply obtain a BoxOff ice object like this, and cache it:
BoxOffice boxOffice = (BoxOffice) beanFactory.getBean("boxOffice");
Most likely, this won't even be necessary: objects that need to use the BoxOffice interface can simply expose a bean property and have the application context set it to the boxOffice reference at run time
This approach has the advantage of requiring no custom code to access an EJB We need only define a
bean using the custom definition
This is the approach used in the sample application, although it would be simple to switch to the Typed Service Locator approach, code for which is included in the download
See the code in the com interface21 ejb access
LocalStatelessSessionProxyBeanDef inition class for the implementation of
this approach
Using the Business Delegate Pattern for Remote EJB Access
A higher level of decoupling is achieved by the Business Delegate pattern, in which a client-side object
exposes business methods that are implemented by calls to the EJB This is more of a facade than a proxy approach, as the business delegate may change method signatures - for example, changing exception types or combining method calls, or even invoking multiple EJBs
As the Business Delegate pattern requires more work to implement than the Service Locator, I'll focus on its use with remote interfaces, with which it delivers real value (its benefits are reduced with local EJB access) The benefits of a business delegate approach include:
o The business delegate may be able to retry failed transactions if this is indicated, without calling code being complicated
o It's possible to handle exceptions from the server in a consistent way without every client
needing to catch J2EE infrastructure-related exceptions such as java.rmi.RemoteException and javax.naming.NamingException The business delegate can catch such low-level exceptions, log them and throw an application-specific exception that makes more sense to client code Or it may be appropriate to wrap EJB-tier exceptions as unchecked exceptions to simplify client code This makes sense when exceptions are fatal - for example, if we've
established that there's no point retrying after encountering a remote exception that prevents execution of a use case
o It may be possible to implement operations at a higher level of abstraction For example, if a particular use case requires multiple EJB invocations, these may be able to be performed in the business delegate
o It may be possible to introduce caching by providing a caching implementation of the
business delegate interface This will benefit all clients
423
Trang 5The drawback of using a business delegate is the potential duplication of the business interface exposed by the EJB tier on the client-side However, using a business delegate offers an opportunity to provide an interface that best fits the needs of clients For example, we can simplify the interface exposed by the EJB tier by omitting irrelevant operations We can simplify client code by only throwing checked exceptions if they're part of the API, and the client can be expected to handle them.
It's superficially attractive for the business delegate to implement the session bean's business methods interface However, this fails to decouple EJB tier and client tier, and cannot deliver some of the benefits we've identified, such as concealing remote exceptions and simplifying the client API Hence, I don't recommend copying
method signatures in the business delegate (the approach recommended in EJB Design Pattern If this is
appropriate, the Service Locator pattern is simpler and should be preferred
Our framework provides easy support for implementing business delegates The same superclass we used for the typed service locator, AbstractLocalStatelessSessionServiceLocator, can be used as a superclass for a business delegate Instead of merely returning an instance of the EJB on demand, a subclass would implement business operations, calling the EJB as necessary
As the sample application uses EJBs with local interfaces, there is no need to use the Business Delegate pattern, which requires more work to implement than a service locator
The following example illustrates the use of a business delegate to access a remote EJB It uses a hypothetical EJB with a remote interface that exposes two int values: one of which can be cached, and one
of which cannot We saw this EJB's business methods interface and component interface under
Implementing EJBs above The remote interface exposes the following business methods:
int getUncacheableValue() throws RemoteException;
int getCacheableValue() throws RemoteException;
By using the Business Delegate pattern, we can simplify calling code by hiding remote exceptions and rethrowing unchecked fatal exceptions, and improve performance by caching the cacheable data value for a given period
When using the Business Delegate pattern, it is good practice to define a business delegate interface, which application code will work with This enables a caching implementation to be substituted without impact on client code, for example The interface might look as follows for this EJB:
public interface MyBusinessDelegate {
int getUncacheableValue() throws FatalException;
int getCacheableValue() throws FatalException; }
The following implementation extends the AbstractRemoteStatelessSessionServiceLocator framework superclass, which performs the necessary JNDI lookup and provides it with an EJB home it can cache in the setEjbHome() method:
public class MyBusinessDelegatelmpl
extends AbstractRemoteStatelessSessionServiceLocator
implements MyBusinessDelegate {
private static long MAX_DATA_AGE = 10000L;
private CalculatorHome home;
424
Trang 6Infrastructure and Application Implementation
It's important to abstract access to EJBs With EJBs with local interfaces, use the Service
Locator pattern, or the Business Delegate if there is a good reason that calling code
shouldn't work with the EJB interface With EJBs with remote interfaces, use the Business
Delegate pattern, and conceal remote access details from clients.
Using JMS
Like JDBC, JMS is a complex API Working with it directly can obscure the purpose of application code Thus an abstraction layer is appropriate The com.interface21.jms.JmsTemplate class applies the same approach as the JdbcTemplate we examined in Chapter 9 We should try to conceal in
Trang 7Let's consider com.interface21.jms.JmsTemplate class's support for Pub/Sub messaging On startup,
a JmsTemplate instance does a JNDI lookup for the TopicConnectionFactory The JNDI name is passed into the constructor, as it may differ between application servers The following method uses the cached TopicConnectionFactory to invoke a callback interface to create a message given a
TopicSession, which it then publishes:
The com interface21.jms.JmsException exception is unchecked, meaning that we have a choice as
to whether to catch it Calling code can implement the PubSubMessageCreator inner interface of JmsTemplate (shown below) without deep knowledge of JNDI:
public interface PubSubMessageCreator {
Message createMessage (TopicSession topicSession) throws JHSException; }
426
Trang 8Infrastructure and Application Implementation
Thus a message can be published with the following code:
Message consumers can also benefit from a simplifying infrastructure Note that it's often best to use MDBs as message consumers, as the EJB container provides valuable infrastructure for JMS message consumption; however, we can't always use EJB We always need to implement the MessageListener interface However, we don't want to have to deal with the complexity of registering a listener with a topic, which also involves multiple JNDI lookups The JmsTemplate class provides a convenience method for this, with the following signature:
public TopicConnection subscribeToTopicNonDurable (String topicName,
MessageListener listener, String messageSelector)
throws Jms Except ion;
A custom bean definition of class com.interface21.jms.JmsListenerBeanDefinition can automatically register any JMS listener to a given topic The following example is from the sample application, in which the
com.wrox.expertj2ee.ticket.referencedata.support.DataUpdateJmsListener JMS listener notifies caching objects of a data update:
427
Trang 9This simple abstraction doesn't take care of all JMS requirements - the JMS API includes many features such as transacted sessions, which pose more problems for abstraction However, it provides a simple infrastructure for the commonest needs.
Implementing Business Logic
As a professional developer, you know how to program in Java What I want to discuss here is how to put
an application together and how to focus your programming skills on your application's domain Thus the following discussion of the sample application does not address low-level details such as the
implementation of the adjacent seating algorithm, but focuses on how the sample application's
components are configured and work together
We'll look both at how the sample application uses standard J2EE infrastructure and how it uses the infrastructure we've discussed so far in this chapter
The sample application's code is available in the download accompanying this book Please refer to it a necessary during the following discussion, as it's impractical to provide a complete listing of all the classes mentioned
Implementing the Sample Application
Let's now consider some of the key business logic of the sample application Let's focus on two key issues: how we obtain reference data about genres, shows, and performances; and how we obtain information about seat availability and implement the booking process
These use cases are described in detail in Chapter 5, so I'll just provide a quick summary of the requirement here, along with the high-level design decisions taken in the last few chapters concerning them
428
Trang 10Infrastructure and Application Implementation
Genre, show, and performance data changes rarely and can be shared between all users of the
application The retrieval of this data need not be transactional
The booking process requires transactional data access, and involves the holding of user session state We have chosen to hold user session state in an HttpSession object in the web container
Defining Business Interfaces
The first step is to define the necessary business interfaces These will not be web-specific; however, they will run in the web container in a collocated application
These interfaces should be able to be implemented with or without EJB without affecting code that uses them In a collocated application such as our sample application, we'll use the Service Locator pattern, discussed above, to hide the details of EJB access completely
There is only one area in which retaining the option of using EJB affects our design: exception handling As EJBs handle unchecked exceptions as fatal system exceptions, as we saw in Chapter 10, we are unable to use unchecked exceptions in a business interface that might be implemented by an EJB Occasionally this calls for compromises
Consider the case of a request to find the number of seats free for a performance, given a performance ID that doesn't match any performance Such a request cannot occur in our application, as the user can only request availability using links from pages that will provide valid performance IDs Thus it makes no sense
to complicate application code by catching a checked NoSuchPerformanceException
However, given that we would like the ability to implement our BoxOffice interface using EJB, we must choose the lesser of the following two evils: make all our application exceptions (including
NoSuchPerformanceException) checked and cope with the overhead of catching checked exceptions where this serves no useful purpose; or lose the ability to switch between EJBs and other business object implementations transparently In the present application we opt for the use of checked exceptions We could use the Business Delegate pattern, to catch checked exceptions from EJBs and rethrow unchecked exceptions However, this requires more work to implement
In the sample application, we will want two separate interfaces to handle the functionality we're
considering here: one to deal with reference data, and one to deal with availability and the booking process Let's consider each in turn
The com.wrox.expertj2ee.ticket.referencedata Calendar interface handles reference data requirements:
public interface Calendar {
List getCurrentGenres();
Show getShowtint(id) throws ReferenceDataException;
Performance getPerformance(int id) throws ReferenceDataException;
}
The Genre, Show, and Performance objects are lightweight value objects, disconnected from the data source They expose a tree structure, navigable downwards: from a genre we can find shows, and from each show we can get a list of performances The entire tree can be cacheable for up to one minute, or as long as we know that there has been no change to reference data (the complexity of caching objects individually is unwarranted, as it would deliver little benefit)
429
Trang 11The com expert J2ee ticket boxof f ice.BoxOf f ice interface exposes information about seating availability and exposes the booking process It includes the following methods:
The first two methods expose seat availability The allocateSeats( ) method creates a reservation, while the confirmReservation( ) method turns an existing reservation into a purchase There is little opportunity for caching here, and the two booking methods are transactional
The ReservationRequest object, used as a parameter to the allocateSeats( ) method, is a command, as is the PurchaseRequest parameter to the confirmReservation( ) method The use of command objects has the following advantages, where multiple individual parameters would otherwise
be required:
o It's possible to add parameter data without breaking method signatures
o Commands provide an excellent basis for event publication if necessary, using JMS or a simple Observer implementation For example, we could easily publish an event with a command attached to enable an e-mail to be sent when a user makes a successful reservation
o It may be possible to bind HTTP request parameters values onto a command Most MVC web application frameworks can automatically populate JavaBean properties of command objects from HTTP request parameters This can greatly simplify application request processing code in the web tier
A listing of the ReservationRequest command shows that such commands are merely simple data holders This object is serializable not to enable remote invocation, but to allow it to be stored in an HttpSession, which may need to be replicated across a cluster or written to a persistent store if swapped out of application server RAM:
public class ReservationRequest implements Serializable {
private static final long MILLIS_PER_MINUTE = 1000L * 60L;
private int performanceID;
private int seatsRequested;
430
Trang 13Infrastructure and Application Implementation
Business interface return values, such as the Reservation object, are coded to interfaces This enables implementations to return an immutable implementation, to prevent manipulation - inadvertent or malicious - by calling code
Determining Implementation Strategy
The Calendar interface involves non-transactional data access Thus there is no need to implement it using an EJB; we can use DAOs in the web container
The BoxOffice interface involves transactional data manipulation Thus a stateless session bean with a local interface is an obvious implementation choice, because using CMT can simplify application code These decisions will not affect code that uses these objects Thus if we wish to increase or decrease use of EJB in the future, or use a different data-access strategy, there will be limited impact on the codebase Let's consider the implementation of the Calendar interface first The
com.wrox.expertj2ee.ticket.referencedata.jdbc.JdbcCalendar class provides a
threadsafe implementation using the JDBC abstraction packages discussed in Chapter 9 The source code is similar to that shown in Chapter 9 The JdbcCalendar class implements the
Trang 14Infrastructure and Application Implementation
As all other application objects are coded to use the Calendar interface, rather than the
JdbcCalendar implementation, caching can be handled by the
com.wrox.expertj2ee.ticket.referencedata.support.CachingCalendar class,
which proxies any underlying Calendar implementation (such as a JdbcCalendar) and
uses copy-on-write to construct a new Calendar implementation when its refresh()
method is invoked Such use of a caching proxy is a natural idiom for interface-based
design, which can significantly benefit performance while keeping caching logic separate
from application implementation
Implementing the BoxOffice
The BoxOfficeEJB uses two helper objects: the BoxOfficeDAO interface, which hides the details of data access behind an abstraction layer and which we looked at in Chapter 9; and the CreditCardProcessor interface, which provides the ability to process credit card payments The sample application uses a dummy implementation of this interface; in a real application, an implementation would probably contact an external payment processing system
The com.wrox.expertj2ee.boxoffice.ejb.BoxOfficeEJBLocal EJB component interface
extends the BoxOffice interface (its "business methods" interface) as well as
javax.ejb.EJBLocalObject, as required by the EJB specification:
public interface BoxOfficeLocal extends BoxOffice, EJBLocalObject {}
The local home interface, com.wrox.expertj2ee.boxoff ice.ejb.BoxOfficeHome, specifies the single create method required of all stateless session beans:
public interface BoxOfficeHome extends EJBLocalHome {
BoxOfficeLocal create() throws CreateException; }
The bean implementation class will extend our
com.interface21.ejb.support.AbstractStatelessSessionBean framework class and
implement the BoxOffice business interface:
public class BoxOfficeEJB extends AbstractStatelessSessionBean
implements BoxOffice {
Extending AbstractStatelessSessionBean forces us to implement a method with the signature ejbCreate(), matching the create() method on the home interface, which we use to obtain and cache
in instance variables helper objects defined in the bean factory:
public void ejbCreate() {
logger.config("Trying to load data source bean");
Trang 15The corresponding bean definitions in the ejb-jar.xml deployment descriptor use the JNDI bean factory syntax described earlier in this chapter, as follows:
This method will throw only checked exceptions, as we want to preserve exceptions for clients:
public Booking confirmReservation(PurchaseRequest purchase)
int purchaseld = dao.createPurchaseRecord(purchase.getReservation());
String authorization = null;
We use CMT to roll this data update back if the credit card transaction is declined (in the highlighted code below) In the unlikely event that payment is accepted, but we fail to update the purchase record with the authorization code (a fatal internal error), we create a log record but throw a checked exception Throwing a runtime exception would result in the transaction being rolled back, which would lose the new purchase record, which should remain in the database:
Trang 16Infrastructure and Application Implementation
By using EJB with CMT, we've simplified our application code: transaction management adds
minimal complexity
Note that we can catch the generic com.interface21.dao.DataAccessException described in Chapter
9 without introducing dependency on the use of JDBC to implement the BoxOfficeDAO data access interface
Using JMS to Propagate Data Updates
For efficiency we choose to cache reference data until we know that it has been updated However, such updates must work across a cluster of servers, so we cannot use the support for the Observer design pattern built into our application framework - we must use JMS A JMS listener will cause cached data to
be refreshed on messages to a data update topic When the Admin interface is available, it will publish JMS messages on changes to reference data; before the Admin interface is completed, administrators will need to access a restricted URL that will cause a reference data update event to be published after updating the database
Note that as the cached data isn't held in the EJB container, we cannot use an MDB as a
Trang 17Pulling It All Together
Now we have our interfaces and implementations, we can use our application framework to hook them
up The following excerpts from the ticket-servlet.xml application context XML definition file illustrate how the application components we've discussed are wired up as JavaBeans
The following two bean definitions define a Calendar implementation that uses JDBC, and a "caching calendar" that creates new objects using the realCalendar definition when it receives update events Note that the singleton attribute of the realCalendar definition is set to false, so a new object will
be returned each time the cachingCalendar asks for an instance of this bean definition:
The following custom bean definition, discussed earlier, registers aJMS listener that notifies the
CachingCalendar object when reference data is updated:
<bean name="referenceDataListener"
definitionClass="com.interface21.jms.JmsListenerBeanDefinition">
<cus tomDe f ini t ion property="1i stenerBean">
com wrox.expertj 2ee.ticket.referencedata.support.DataUpdateJmsListener
Trang 18Infrastructure and Application Implementation
com.wrox.expertj2ee.ticket.boxoffice.BoxOf f ice business interface
Using the infrastructure discussed in this chapter, application components that use these interfaces need simply expose bean properties that can be set by the framework appropriately: they don't need to depend
on framework code and they don't need to look up objects managed by the framework For example, the application's main web-tier controller exposes calendar and boxOffice properties, which the
framework sets automatically based on the following bean definition:
<bean name=" ticketController"
class= "com.wrox.expertj2ee.ticket web.TicketController">
// Other properties omitted
<property name="calendar" beanRef="true">calendar</property>
<property name="boxOf f ice" beanRef="true">boxOff ice</property>
to use, ensure that application code focuses on addressing business problems, and solve problems that the J2EE specifications do not
To illustrate these points, and to provide a potential basis for your own applications, we've examined some of the key infrastructure packages used by the sample application, and the problems they exist to solve
Trang 19We've considered the importance of infrastructure that enables central configuration management We've seen how making application components JavaBeans can allow them to be configured by a generic application framework, removing the need for application code to implement configuration management such as property file lookups In combination with interface-based design - in which code is always written to interfaces, rather than classes - the use of JavaBeans is an incredibly powerful means of assembling and parameterizing applications without modifying Java code.
Once configuration management is centralized, it is possible to read configuration from any source - such as XML documents, properties files, or JNDI - without any need to change application code Configuration via JavaBean properties ensures consistency across applications and between architectural tiers For example, EJBs can use the same approach to configuration management as ordinary Java classes
We've looked at several layers of infrastructure that enable us to achieve central configuration management:
o A package that abstracts the use of reflection to manipulate JavaBeans This is valuable basic infrastructure that can be used not only in configuration management, but also for other tasks such as initializing JavaBean objects from HTTP requests (as discussed in the next chapter), simplifying request processing in web applications This functionality is so useful that several open source projects, such as Apache Commons, provide their own implementations; however, I am yet to see
a really good one, and the com interface21 beans package described in this chapter is the most sophisticated I know of
o A "bean factory" that uses this lower-level functionality to maintain a registry of application objects When application objects are defined as JavaBeans, they need not depend on this infrastructure; infrastructure can work behind the scenes to set bean properties to configure objects, including setting relationships between objects, based on definitions held outside Java code
With the combination of built-in support for primitive properties, the ability to set relationships between managed objects, and the availability of the standard JavaBeans PropertyEditor
functionality to represent object types as strings, concise bean factory syntax can define complex application configurations We looked at standard bean factory implementations based on XML documents, properties files, and environment variables defined in EJB JAR deployment
descriptors
o An "application context" that builds on the bean factory concept to implement the Observer design pattern, provides a standard way to look up messages to meet internationalization requirements, and allows application components to communicate as necessary at run time
The standard J2EE infrastructure makes some simple things difficult, which has caused widespread frustration withJ2EE in practice This can be addressed by a simplifying infrastructure that makes J2E APIs and services easier to work with We've seen how infrastructure can be used to simplify working with complex J2EE APIs, including:
o Support classes that make it easier to implement EJBs and reduce the likelihood of
common errors
o Infrastructure classes that help to implement the Service Locator and Business Delegate J2I patterns, decoupling code that uses EJBs from dependence on JNDI and EJB APIs We saw that the Service Locator pattern is usually simplest and most appropriate for accessing EJBs through local interfaces, while the Business Delegate is the better choice to access EJBs through remote interfaces In this case the Business Delegate can be used to conceal the details of remote access from client code
438
Trang 20Infrastructure and Application Implementation
o Classes that make it easier to send and consume JMS messages, concealing the necessary JNDI lookup, simplifying error handling and the need to work with multiple JNDI API objects
Whether or not you choose to use the specific infrastructure described in this chapter, all these issues should be addressed outside application code, which should concentrate on the problem domain and implementing business logic
Finally, we looked at some of the core implementation of the sample application, illustrating how it is simplified through use of appropriate infrastructure
439
Trang 21After surveying the problems we seek to avoid in web application design, we'll focus on the MVC architectural pattern, which has proven the key to writing successful web interfaces.
After examining MVC theory and the concepts shared between successful implementations of the MVC pattern, we'll look at three open source MVC web application frameworks: Struts, Maverick, and WebWork We'll then look at the design and use of a simple but powerful web application framework integrated with the infrastructure that I introduced in the last chapter, and which we'll use for the sample application Like the infrastructure components we've seen so far, this framework is intended for use in real applications, and not merely as a demo
I chose to implement this framework rather than use an existing framework for several reasons:
o To demonstrate the potential to integrate an MVC web application framework into an overall application infrastructure, and the benefits that this brings
o To formalize the relationship between web-tier components and business objects, which are independent of the web interface I feel that the relationship between web-tier components and business objects is often neglected in MVC applications, with too much application code being web-specific
441
Trang 22o Because I have considerable experience in this area and have implemented previous production frameworks, from which this is a natural evolution.
o Because no existing MVC web framework meets all the design goals we will discuss
o Because the design of this framework clearly illustrates the MVC concepts central to this chapter, and especially the complete decoupling of views from controllers We'll see the benefits of this when we look at alternative view technologies in the next chapter
Naturally, this framework builds not only on my experience, but also on the concepts used in existing frameworks It borrows some concepts from Struts and Maverick, in particular
We'll also look at the issue of session state management and data validation in web applications.Finally, we'll look at implementing the web tier of the sample application using the MVC framework discussed in this chapter, paying special attention to how web components access business objects
The Challenges of Web Development
Why is designing web interfaces so hard and why are the consequences of getting it wrong so dire? Some
of the many reasons include the following:
o Web interfaces change frequently
For example, re-branding may change the look and feel, but not necessarily the workflow, of
a web application A successful application can accommodate such change without the need to change business objects or even web-tier control code
o Web interfaces involve complex markup
Typical web sites contain nested tables and lengthy JavaScripts; pages may even be designed using GUI tools that produce hard-to-read markup Usually only a small proportion of the content is dynamic It's vital that the generation of complex markup doesn't obscure the generation of dynamic content Getting this markup right is the business of specialists; Java developers are seldom good at it and their skills are better employed elsewhere Thus separation of the roles of markup developer and Java developer
traditional MVC approach such as Java Swing
o HTTP requests can carry only string parameters
Thus we frequently need to convert string values to and from Java objects This can be difficult and error-prone
o Web interfaces make it difficult to validate user input, as we have limited control over the client browser
We must be prepared to reject invalid user input and make users resubmit it In contrast, we can use custom controls to prevent users entering invalid data in Swing or VB interfaces, as
we have complete control over UI presentation
442
Trang 23Web-Tier MVC Design
o HTML offers a limited choice of UI controls
Again, this is in sharp contrast to traditional GUI applications, which offer a rich range of controls and the ability to implement custom controls
o Ensuring that a web site looks right and works correctly in all common browsers can be difficult
This can significantly complicate markup and add to the challenge of separating presentation issues from Java programming issues
o There are many efficiency considerations
Often we cannot restrict or predict the number of users using a web application Both performance and concurrency are likely to be particularly important in web applications
o Web interfaces are relatively hard to test
This is one of many reasons that we should ensure that as little application functionality as possible is web-specific
Lessons Learned in Java Web Development
Designing web interfaces to achieve maintainable and extensible applications requires discipline The best motivation for achieving this discipline is awareness of the consequences of failing to achieve it The following brief discussion of the history of Java web development serves to illustrate the dangers that we must avoid
The Shortcomings of Servlet-only Solutions
The first Java web technology was servlets The first release of the Servlet API back in 1997 was an
improvement on the dominant existing technology, the primitive Common Gateway Interface (CGI)
standard for server-side script invocation Instead of scripts written in languages such as C or Perl, which usually caused a new process to be created to handle each request, servlets allowed dynamic content to be generated by reusable Java objects Servlets had full access to the power of the Java language, including JDBC and the Java object model, and so elevated web development to true OO development
At least in theory - the Servlet specification was a big step forward, but developers soon ran into problems building real applications Servlets proved very good at invoking business objects However, generating complex markup from Java code is awkward If markup strings are embedded in servlet and helper classes, changes to the presentation of web content always require modification of Java code and recompilation; Java developers must always be involved Furthermore, it can be very difficult to see how a complete page
is assembled from markup littered throughout Java objects, making sites very difficult to maintain Servlets proved much more effective at generating binary content However, this is a rarer requirement
Generating markup from Java objects such as servlets is clumsy and leads to
unmaintainable web applications The servlet specification alone is not enough to meet the
challenges of most web applications.
Trang 24It was clear that additional infrastructure was needed Many developers used templating approaches, in which
markup was held outside Java code, with servlets responsible for generating only the dynamic content However, there was no widely adopted standard
JSP: Promise and Temptation
Thus the release of the JSP 0.92 specification in 1998 created widespread excitement JSP was a standard templating solution, endorsed by Sun Although JSP pages are transparently compiled into servlets by a web container, JSP pages use a very different approach from servlets While servlets are Java objects capable of generating markup, JSP pages are markup components that permit escaping into Java as necessary to initiate processing
JSP was soon in wide use, and overenthusiastic adoption led to a host of new problems Experience quickly demonstrated that a model based on escaping into Java as necessary to generate dynamic content was a new evil in place of the old need to generate markup from Java code The release of JSP 1.0 in 1999 was a significant improvement on JSP 0.92, but did not change the underlying model or address these problems
"JSP Model 1" Architecture
To understand these problems, let's consider the simplest and most obvious architecture for a J2EE web application's interface Imagine that we use JSP pages to implement the entire web interface Each JSP handles incoming requests, creating domain objects from request parameters and using them to invoke business objects Each JSP then renders the result of the business processing The JSP infrastructure makes it easy to implement this approach, using JSP pages with a mix of markup and embedded Java code The standard JSP infrastructure allows JSP pages:
o To automatically populate JavaBeans declared on them with request parameter values, using the
<jsp:useBean> standard action
o To access and manipulate session attributes and application-wide ServletContext attributes
o To perform any kind of logic using scriptlets (blocks of Java code embedded in JSP pages)
This kind of web application architecture is often termed the JSP Model 1 architecture Core J2EE Patterns uses the more meaningful, but rather overblown, term, Physical Resource Mapping Strategy I' use the older
term, JSP Model 1, in this chapter
This approach is simple to implement All we need to do is write the JSP pages and provide the support classes There is no need to write servlets or define URL-to-servlet mappings in the web.xml deployment descriptor Development-deployment cycles are often short, as JSP pages can be recompiled automatically by the web container as they are modified
444
Trang 25o Broken error handling What if, halfway down the JSP, after the buffer has been flushed so it's too late to forward to another page, we encounter an exception? Although the standard JSP error page mechanism works well enough for trivial Model 1 systems, it won't help us in this plausible scenario
o The web interface is tied to JSP In fact, JSP is just one view technology available to J2EE applications JSP pages are unsuited to presenting binary data, and other technologies such as XSLT are superior at tackling some problems
o Java code in JSP scriptlets is hard to test and cannot be reused Not only is this approach not object-oriented, it fails to deliver even the code reuse we would achieve in a well-written
The JSP Model 1 architecture, in which JSP pages handle incoming requests, results in
non-maintainable applications JSP Model 1 should never be used in real applications: its
only use is in prototyping.
The Temptation of the JSP Standard Infrastructure
Hopefully you're already convinced that JSP pages are not the ultimate answer to J2EE web interfaces In case you aren't, let's consider some of the shortcomings of the JSP standard infrastructure, which makes
it easy to attempt many things that subvert good design, and makes the JSP Model 1 architecture plausible
on first impression
Request parameters can be mapped onto JSP beans in two ways Individual request parameters can be mapped onto a bean property using a <jsp:setProperty> action like this:
<jsp:setProperty name="bean" property="propertyName" param="paramName" />
Alternatively, all request properties can be mapped onto a bean in a single <jsp:setProperty>
action like this:
<jsp:setProperty name="bean" property="*" />
445
Trang 26The JSP mechanism for mapping request parameters onto bean properties is an unrealistically naive
approach to data binding (a problem we'll discuss in detail later) Problems include the following:
o There is no mechanism for handling type mismatches For example, if the value of a parameter
corresponding to an int property is non-numeric, the mapping will fail silently The int property on
the bean will be left with its default value The bean isn't capable of holding the invalid value the user supplied on any resubmission page Without checking the request parameter directly, we can't
distinguish between a missing parameter and a type mismatch
o The only way to establish whether a parameter was supplied is to set the default to an out-of-band value This may not always be possible
o There's no mechanism for handling any exceptions thrown by the bean's property setters
o There's no way to specify which properties are required and which are optional
o We can't resolve these problems by intercepting the handling of the fields: we have no control over how the mapping is performed
We'll end up needing to write our own request handling code in the JSP to address these shortcomings, which defeats the purpose of the property mapping infrastructure
The standard JSP infrastructure for mapping request parameters onto JSP beans is
primitive and next to useless in real applications.
Another tempting feature of the JSP standard architecture, which encourages Model 1 architectures, but shouldn't be used in real applications, is the ability to define methods and even classes in JSP pages Normally code in scriptlets ends up in the JSP's _jspService() method By using <%! syntax instead of <%
we can ensure that embedded Java code goes outside this method, defining methods, variables, and classes in the generated class itself
JSP is very powerful, but its power creates as much danger as benefit JSP lets us do anything we might legitimately want to do in a view, and a lot of things that we're best not to attempt Since its initial release, JSP
has gained new capabilities: most significantly, the addition of custom tags, which enable Java helper code to
be concealed behind markup However, the basic model hasn't changed, and nor has the fact that JSP pages are excellent at displaying data, and ill-suited to request processing and initiating business logic
Striking a Balance
Both the approaches discussed so far - servlet-only and JSP Model 1 - have severe disadvantages and make it impossible to achieve separation of responsibility between Java developer and markup developer roles Such separation is essential on complex web sites
However, by using the two technologies together - servlets to handle control flow, and JSP pages or another view technology to render content - we can avoid the problems I've described
446
Trang 27Web-Tier MVC Design
A so-called JSP Model 2 architecture introduces a controller servlet as the central entry point, rather than
individual JSP pages The controller is responsible for request processing, and chooses a JSP to render content depending on request parameters and the result of the necessary business operations In fact, as we'll see, there are many refinements to this model: it's important to avoid having a single "God" servlet, but ensure that the servlet works with a number of sub-controllers
I won't refer to this as JSP Model 2 architecture, as in fact this architecture isn't dependent on JSP: it works with any templating technology Its basis is servlets - by far the more important of J2EE web technologies
I'll use the term Front Controller pattern, which is used by Core J2EE Patterns This is an attempt to apply
the MVC architectural pattern (which we'll discuss further below) to web applications
This pattern, which we'll discuss in detail below, is the key to designing maintainable J2EE web
applications, and the central point of this chapter
Both servlets and view technologies (such as JSP) are required for building maintainable
J2EE web applications Servlets and helper classes should be used to handle control flow
and initiate business operations; JSP should only be used to render content.
Web-Tier Design Goals
Enough negatives: what features should a good web interface have? The following two goals are central to
maintainability and extensibility
A Clean Web Tier
As JSP Model 1 experience shows, a web application is doomed to failure if markup generation is inextricably mixed with control flow and business object invocations
A clean web tier separates control flow and the invocation of business objects (handled by
Java objects) from presentation (handled by view components such as JSP pages).
Java classes should be used to control flow and initiate business logic Java classes should not be used to
generate markup It should be possible to change presentation markup without modifyingjava classes.JSP pages - or whatever templates are used - will contain only markup and simple presentation logic This permits an almost complete separation of Java developer and markup author roles
A Thin Web Tier
However, achieving a clean web tier is not enough It's also vital to ensure that the web tier is as thin as
possible This means that the web tier should contain no more Java code than is necessary to initiate business processing from user actions, and display the results It means that the web tier should contain only web-specific control logic (such as the choice of the layout data should be displayed in) and not business logic (such as data retrieval)
447
Trang 28Why is this so important? Applications in which business logic is tied to the webtier - even web tier Java
objects such as servlets, rather than JSP pages - fail to meet the goals we identified in Chapter 1 Such applications:
o Are hard to test - a very important consideration Testing web interfaces is much harder than testing ordinary Java objects If we expose business objects asJavaBeans implementing well-defined interfaces, we can easily write test harnesses that test business logic in isolation from any user interface In applications in which business objects expose well-defined interfaces, testing the web interface is a comparatively simple problem of checking that the interface correctly translates user actions into the correct business requests and correctly displays the results If an application's web
tier contains business logic, we can only test the business logic through testing the web interface as
a whole, which is likely to be much harder
o Reduce potential to reuse business logic code, even within the same web application Code in a servlet's service() method, for example, won't be available even to other servlets
o Are likely to prove vulnerable to changes in web interface workflow (although not necessarily to changes in presentation) If we have a distinct layer of business interfaces, we can easily run regression tests at any time If some of our business logic is in the web tier, it's likely that changing the workflow will introduce bugs Tests against the web interface will need to be rewritten to reflect the changed workflow, making testing the new workflow problematic
o Aren't pluggable If we expose business operations in a layer of business interfaces, we can easily change the implementing class of one or more business objects without affecting calling code (assuming we have a framework that facilitates interface-based design, like the one I discussed last chapter) If business operations are tied to the web tier, there's unlikely to be such a straightforward way to change implementation
o Make it hard to expose an interface other than a web GUI, such as a web services interface
In a well-designed J2EE application, the web tier should be a thin layer built on
well-defined business interfaces It should be responsible only for translating user
actions into application events, and the results of processing user input into results
displayable by the web interface.
This point deserves to be emphasized Although a thin web tier is as important as a clean
web tier, the relationship between web tier and business objects seems to get far less
attention than the MVC approach to ensuring a clean web tier.
MVC Concepts and the Front Controller J2EE Pattern
Let's look in more detail at what it means to adopt an MVC approach to web applications In this section we'll consider theory and general principles In the next section we'll look at some real MVC web frameworks that simplify the implementation of applications using an MVC approach
448
Trang 29implementing the ListModel interface, which publishes ListDataEvents when the data changes The JList view component provides a ListDataListener implementation that registers with the ListModel
and updates the display as events are received This is a push model: the model pushes notifications to
any number of listeners, which are typically (but not necessarily) views
As changes in a web application are usually only displayed to the user when a request is received and a new page is generated, the push model doesn't work Hence a web application view will only render once It
won't help to register it to receive ongoing notifications The solution is to use a pull model, in which a view
is given access to the models required to render a dynamic page, and the view pulls data from the model as necessary Since a web page may contain different controls and sections of dynamic data, more than one model may be required to back it
Whether we use a push or pull model, the MVC architecture still delivers its key benefits Each
component type has a clear responsibility Models contain no view-specific code Views contain no control code or data-access code and concentrate on displaying data Controllers create and update models; they do not depend on particular view implementations
Although the MVC approach has the potential to improve web-tier code tremendously, it is a little complex to set up Thus we will generally use a generic MVC web application framework, rather than implement the pattern ourselves
Controller
As the controller is central to MVC in a web application, let's consider the controller first
The key responsibilities of a web application controller are:
o To examine and extract request parameters
This may lead to the creation of command domain objects, which will be passed to business objects
An MVC framework usually provides infrastructure that simplifies working with request parameters: for example, by populating JavaBeans with parameter values
o To invoke business objects, passing the data extracted from the request
Controllers will usually catch application exceptions resulting from business method invocations, modifying model creation, and view selection accordingly (However, some fatal exceptions can simply be left to be handled by the web container, as web applications have a standard "error page" mechanism.)
449
Trang 30o To create the model that views will display based on the results of invoking business methods and any session state
o In applications with server-side state, to create and manipulate session state
o To choose a view and cause it to render, making the model data available to it
Other responsibilities may include generating web-tier log output and enforcing security restrictions (although security can also be handled declaratively inJ2EE, as we saw in Chapter 6)
As these responsibilities are a mixture of application functionality (such as invoking application-specific business objects) and plumbing (such as passing model data to a named view and causing it to render content), they are usually split between application-specific and generic framework classes when using an MVC web application framework
It's vital that controller objects have easy access to application business objects Neither model nor view objects should require such access
Model
A model contains data displayed by a view In the J2EE web application, the model normally consists of JavaBeans Many view technologies, including JSP, work best if models are JavaBeans Models in web applications (as opposed to "true," traditional MVC) are usually dumb storage objects, such as value objects, that represent the result of a complete business operation Once a controller completes its processing and selects a view to generate content, the model should contain all the data to display The model itself should not perform any further data access, and it should not contact business objects There
is no reason for a model to depend on the Servlet API or a web application framework; JavaBeans used as part of a model need not be web-specific They will often be domain objects, usable outside the web tier
In the JSP paradigm, models are added to the request as attributes using the
request.setAttribute() method However, this isn't the only way to communicate a model to a view; not all views may find request attributes useful
View
A view displays the information contained in a model, taking entire responsibility for markup or other content generation A view needn't know anything about the implementation of the controller or the underlying business objects
JSP is the most used view technology for J2EE applications, although, as we'll see in the next chapter, there are several valid alternatives We've noted that JSP gives us the power to do things that are
inappropriate in views The following guidelines indicate what operations are appropriate in views:
o A view should be wholly responsible for generating web content, given data models
o A view should not work directly with request parameters A controller should intercept
incoming requests, and provide the view with models
450
Trang 31Web-Tier MVC Design
o A view should not perform data retrieval (for example, by running SQL queries) The view
should merely display data, which will be exposed by one or more models The Dispatcher View
J2EE Pattern (Core J2EE Pattern in which a JSP view retrieves data using a helper as it renders, is fundamentally broken, as it is vulnerable to unexpected errors during rendering Core J2EE
Patterns terms the controller-managed data retrieval described here the Service-to-Worker
pattern (No, I don't understand how they thought up these names, either.)
o A view may need to perform display logic, as distinct from control logic or business logic
Examples of display logic include displaying the rows of a table in alternate colors, or
displaying additional information on a generated page if the model contains optional data Such logic does not belong in controllers, as it's unique to a particular presentation
o A view should not have to deal with data-retrieval failures In practice, this means that properties and methods exposed by models should not throw exceptions; data retrieval should be complete before a view begins to render
Last, and most importantly:
It should be possible to replace any view in a web application with another that displays
the same data without modifying model or controller code A view should be thought of as
meeting a contract: "Display this kind of data." It should be possible to substitute any view
for another without affecting the working of the application This is the key to ensuring
that presentation can be updated without affecting workflow.
It should be possible even to replace a view with another that uses a different view
technology without affecting controller or model code For example, it should be possible
to replace a JSP view with an XSLT view.
Many think that view substitutability is impossible - or prohibitively difficult - to achieve
in practice I believe otherwise, and in this chapter and the next chapter will demonstrate
how it can be achieved, and the benefits it brings.
Of course, unless a view contains the correct links or form fields, it may be impossible to continue using the application But this is a question of displaying available information, not control logic
Giving view components such limited and clearly defined responsibilities is the best way to ensure the separation between the roles of Java developers and markup (presentation) developers that is essential on large web sites
It also has great benefits for testing If we know that our views merely pull data from a model supplied by a controller, we can change presentation by modifying views without any risk of breaking the behavior of our application Testing can be limited to checking that the modified view contains no syntax errors, and displays the correct content
Unit and regression tests will be written for controller components, to check that they initiate the correct business logic operations and expose the correct models It's much easier to write unit tests and regression tests for Java classes than for JSP pages or other rendering technologies, so this works well If, on the other hand, our "views" perform data retrieval, we will need to perform regression testing when we change presentation
451
Trang 32Control Flow
To illustrate these concepts in action, let's consider a possible flow of control in a simple MVC
implementation (Real MVC web application frameworks are a bit more refined, as we'll see.) Imagine that the request is to book several seats of a particular type for a performance of a show Our sample application uses a more sophisticated workflow for this - the following is a stripped down example:
1 A controller servlet receives a request There is a mapping from the request URL to
controller servlet in the application's web.xml deployment descriptor
2 The controller servlet examines the request parameters The performanceId,
seatTypeId, and count parameters are required If any of these is missing or non-
numeric, the user should be sent back to the welcome view, welcome.jsp
3 With valid data from request parameters, the servlet invokes an allocateSeats()
method on a business object that it obtains and stores as an instance variable on
application startup
4 If the attempted booking succeeds, the servlet adds the returned array of Seat objects (the model) to the HttpServletRequest as an attribute and forwards the request to the view displayReservation jsp If there aren't enough seats, the servlet adds the exception containing this information to the HttpServletRequest and forwards the request to the view retryBooking j sp
This simplified workflow indicates how one type of request (usually a request to a particular URL) can lead to multiple views, and how views need only render content after data retrieval is complete.The following diagram illustrates the workflow described in the text:
452
Trang 33Web-Tier MVC Design
Pattern Variants
There are many variants of front controller implementation Let's consider some of them before we move on to discuss real MVC frameworks
Template Selection Servlet
The simplest front controller approach is to use what I'll call a template selection servlet to handle incoming requests Each template selection servlet will process request parameters, invoke the appropriate business objects and conclude by choosing a template and making model data available to it (pretty much the same workflow as in our simple walkthrough above)
As an illustration of this approach, let's look at a popular implementation The WebMacro template solution, which we'll discuss in more detail in the next chapter, provides a convenient generic
superclass, org.webmacro.servlet.WMServlet, for template selection servlets using WebMacro templates as views
Application-specific subclasses of WMServlet need to implement the following method to process the request, initiate the necessary business operations, make model data available to WebMacro, and return the selected WebMacro Template to generate content The request and response objects are available via the WebContext argument:
public Template handle(WebContext context) throws HandlerException
To use this approach, each request URL in our application must be mapped to a separate template servlet
in web.xml On the positive side, this means there's no need for a framework-specific approach to map request URLs to handlers On the negative side, mapping in web.xml is verbose, and we'll need to define
a new template servlet instance for each URL
Note that we can apply this approach very easily using JSP as the templating technology; each servlet
can simply use a RequestDispatcher object to dispatch to the appropriate JSP There is no need for the kind of infrastructure provided by the WMServlet class.
This is a simple, effective approach, and a big advance on a servlet-only or JSP Model 1 strategy However,
it fails to deliver all of the goals we have set ourselves, and I don't recommend it In particular:
o Controllers (in this approach, implemented as template selection servlets) are dependent on a particular view technology We're out of luck if we decide, for example, to change from WebMacro
to XSLT or PDF views This may prove a serious problem, for example, if we need to generate binary output
o We must work with request parameters directly There's no overall framework providing a
higher-level workflow than the Servlet API
o There's no application framework to help us achieve a thin web tier
453
Trang 34How Many Controller Servlets?
There is a debate among MVC proponents about how many controller servlets an application should have Should we use one servlet for each type of request (as in the template selection servlet approach), or a single
front controller that handles all requests to an application or subsystem? Core J2EE Patterns terms the
latter approach the Multiplexed Resource Mapping Strategy If we use a single front controller, it should
be generic, and should forward requests to a number of sub-controllers
Many of the arguments against using a single controller servlet are unsound - such as the idea that it introduces a single point of failure (threads can die, not objects); or that it means that the controller will be too large (we can use a generic controller with many application-specific delegates, rather than one huge, all-knowing controller) One valid criticism is the need to duplicate the standard mapping of URL to request-handling class While we can map URLs directly onto servlets in web.xml, when we use one controller to front many URLs we will usually map all URLs onto the controller, which must then use its own mapping format to route URLs among its sub-controllers However, this isn't a real problem in practice.The advantages of using a single controller include that it ensures consistency of control flow - for example, ensuring that necessary resources are available to all sub-controllers It also means that each sub-controller doesn't have to be a servlet; an important point as we can allow sub-controllers to
implement a less complex interface
It's natural to ask what the performance implications are of the additional overhead in funneling requests through a single controller A typical controller servlet implementation will need to do hash table lookups based on the URL of each request Some frameworks use reflection (although an efficient framework should cache the results of reflection) Fortunately, the performance impact is negligible Pretty much
anything we do once per request will have no measurable effect on the overall performance of a web
application In profiling applications running in MVC frameworks, I've consistently found the framework overhead to be insignificant - even undetectable
All the real frameworks I know of use a single controller servlet for a whole application or a set of related use cases The controller servlet then delegates work to helper classes that implement a
framework-specific interface or extend a base class provided by the framework
JSP or Servlet Controller?
Another key question is whether the controller should be a servlet or JSP Core J2EE Patterns terms these the
Servlet Front and JSP Front strategies respectively, and some Sun examples show the use of a JSP as a
controller (I was guilty of such a sample application myself in my first publication on JSP.)
The JSP Front approach is naive and misguided, and does not deserve to be dignified as a "strategy" JSP pages are poorly suited for use as controllers, for many reasons For example, they are not first rate Java classes (We can't subclass a JSP if we want to customize its behavior, for example.) They also dontfoster code reuse
The only argument in favor of using JSP controllers is that they're a little easier to implement, as we sidestep web.xml servlet mappings: a tiny saving that isn't worth compromising an architecture for
454