JaVa
   

The Web app Framework Used in the Sample app

Hence, for the sample app, I've chosen to use my own web app framework - more precisely, the most recent of several web app frameworks I have written. I considered carefully whether to use an existing framework such as Struts or Maverick. However, I decided that integrated app architecture I wanted to demonstrate could not be achieved so successfully with any existing framework and that no existing framework met all the goals I wanted to achieve. However, the beauty of open source is that I was able to borrow some of the best concepts from these products for my own framework. My experience in developing web app frameworks goes back a long way. I developed my first production web app framework in early 2000 - before Struts or any alternatives were available - and have since refined the concepts. This has given me the freedom, unavailable to the Struts developers, to develop a clean-room implementation based on this experience without concern for backward compatibility. However, it should be noted that my first framework is still in production on several high-volume web sites after two years, and has proven to support maintainable, quality apps.

Important 

The framework described here is not intended merely as a demo, and is not limited to the sample app. It's ready for production use; feel free to use it in real apps.

All the source code, some of which is discussed here, is included in the download with this tutorial, in the /framework subdirectory.

Design Goals

Starting from the basis of the JavaBeans-based infrastructure we discussed in , implementing a web app framework was surprisingly easy. I adopted the following design goals:

This approach makes it much easier to implement a thin web tier, and makes it easy to test business objects outside a J2EE server.

Basic MVC Control Flow

Let's look at how this framework supports basic MVC control flow. The control flow is more similar to that of Struts than the other frameworks I've described, although the view management approach is closer to that of Maverick. Incoming requests are handled by a single generic controller servlet, to which all app URLs are mapped in web.xml. This controller servlet uses its own mappings from incoming requests (not necessarily URL-based, although the default mapping is URL-based) to request controller beans defined in the app context. It's possible to have multiple controller servlets, each with a separate configuration file. These will share a root WebappContext, but be otherwise independent. Each app-specific request controller bean is a threadsafe, reusable object like a Struts action. The following sequence diagram illustrates the flow of control (it's actually slightly simplified, but it illustrates the basic concepts). I'll discuss each of the four messages in more detail below, and we'll look at each class and interface in detail later:

Java Click To expand
  1. On receiving an incoming HTTP request, the controller servlet asks an implementation of the com.interface21.web.servlet.HandlerMapping interface for an app request controller to handle this request. The default implementation of the HandlerMapping interface chooses a controller (implementing the com.interface21.web.servlet.mvc.Controller interface) based on request URL; however a custom implementation could use any approach (actually the controller maintains a list of HandlerMapping objects that are applied in a fixed order until one matches, allowing for more sophisticated mapping than any of the frameworks we've discussed).

  2. The controller servlet calls the request controller's handleRequest() method. This must return an object of type com.interface21.web.servlet.ModelAndView that contains the name of a view and model data to display.
  3. The controller servlet invokes an object of type com.interface21.web.servlet.ViewResolver to obtain a reference to the View object with the name returned by the request controller. A ViewResolver should be able to support internationalization, and so may return a different view instance depending on the request locale.
  4. The controller servlet calls the render() method of the view to generate content. As with Maverick, there's no dependence on any particular view technology.

The core framework workflow doesn't involve the creation of command objects. This is often, but not always, desirable functionality. Including it in the basic MVC workflow would unnecessarily complicate simple interactions where it's overkill to create a command. Such workflow refinements can be implemented by abstract implementations of the com.interface21.web.servlet.mvc.Controller interface, which app-specific controllers can extend, without adding complexity to the basic workflow described above. A variety of different superclasses are included with the framework. The core workflow leaves as much choice as possible: custom controller superclasses can support different kinds of command flow, including the use of command objects as in Struts. Alternatively, adapters can be implemented to allow the use of different workflows without concrete inheritance from framework classes. The following class diagram shows the relationships between the classes and interfaces involved, and how this web app framework integrates with the app framework discussed in the last chapter. It also shows how the ControllerServlet is derived from framework superclasses that provide integration with the root WebappContext. This diagram is a lot to take in one go. It's best used as a reference as you read the following discussion:

Java Click To expand

The com.interface21.web.servlet.mvc.Controller interface, circled in the diagram, is implemented by app-specific controllers. In most apps, there is no need to implement any of the other interfaces or extend any of the classes shown in the diagram, as the default implementations meet common requirements. Let's now consider each important class and interface in turn.

Controller Servlet

The entry point of an MVC web app with any framework is a generic controller servlet. In the Interface21 framework, the controller is a servlet of class com.interface21.web.servet.ControllerServlet. This is a concrete class; there is no need to subclass, as it is parameterized through the app context it runs in. A controller servlet does not itself perform control logic. It is a controller of controllers: its role is to choose an app-specific request controller to handle each request and to delegate request processing to it. A web app may have multiple controller servlets, each associated with a WebappContext (an extension of appContext specific to web apps), although the sample app uses a single controller. All controller servlets in a web app share a common parent app context, enabling them to share as much or as little configuration as they wish. This allows large apps to use one controller servlet for a group of related use cases, keeping the size of each configuration file manageable (as Struts 1.1 allows), while providing a formal mechanism for sharing business objects (as opposed to more haphazard sharing via the ServletContext). The com.interface21.web.servlet.HttpServletBean base servlet class extends javax.servlet.HttpServlet to enable servlet bean properties to be transparently set at initialization time from web.xml servlet <init-param> elements. This superclass can be extended by any servlet, regardless of whether the framework described here is used - it doesn't depend on the MVC implementation. As with our BeanFactory configured by EJB environment variables, this ensures that servlets need only expose bean properties to be configured. There's no need to look up servlet configuration parameters in servlet code, and type conversion is automatic, using a PropertyEditor if necessary. The immediate superclass of ControllerServlet is com.interface21.web.servlet.FrameworkServlet, which extends HttpServletBean to obtain the root WebappContext (which must have been set as an attribute in the ServletContext by the ContextLoaderServlet, which we'll discuss shortly) and create its own independent child context, defining its own beans. It will also have access to beans defined in the parent, global, context. By default each framework servlet's child context will be defined by an XML document (although a bean property can be used to set a custom context class name) with the URL within the WAR /WEB-INF/<servlet-name>-servlet.xml, where the servlet name is set by the <servlet-name> element in web.xml. Thus for the sample app, the context for the single controller servlet instance must be located at /WEB-INF/ticket-servlet.xml. The controller servlet for the sample app is configured in web.xml as follows:

 <servlet>
 <servlet-name>ticket</servlet-name>
 <servlet-class>
 com.interface21.web.servlet.ControllerServlet
 </servlet-class>


 <init-param>
 <param-name>debug</param-name>
 <param-value>false</param-value>
 </init-param>


 <load-on-startup>2</load-on-startup>
 </servlet>


We also need to define a <servlet-mapping> element in web.xml. In this case we want to ensure that this controller servlet handles all requests for .html resources. I prefer this approach to the usual practice with Struts and Maverick, of using .do and .m as the respective default mapping extension. When using virtual URLs, we don't need to expose the technology we use to users:

 <servlet-mapping>
 <servlet-name>ticket</servlet-name>
 <url-pattern>*.html</url-pattern>
 </servlet-mapping>


Note 

Note that when we map .html URLs onto the controller servlet, any static HTML files that may be included in request processing must have an .htm or another extension other than .html, or requests for them will be intercepted by the controller.

In an app using multiple controller servlets, we would probably map individual URLs onto each controller, unless it was logical for each server to handle requests with a different extension or virtual directory. See section 11.1 of the Servlet 2.3 specification for an explanation of the rules for defining mappings in the web.xml deployment descriptor.

All further configuration of the controller servlet is held in the beans defined in /WEB-INF/ticket-servlet.xml. This has the added advantage that there's no need to learn complex DTDs such as the Struts config format: the same property configuration syntax is used for all configuration, web-specific and otherwise, so we simply need to know which properties of which classes we need to configure.

Request to Controller Mapping (com.interface21.web.servlet.HandlerMapping)

The controller servlet chooses which request controller to invoke to handle incoming requests using implementations of the com.interface21.web.servlet.HandlerMapping interface defined in the current bean factory, or attempting to match the request URL to a controller bean name if no HandlerMapping implementation is defined. The sample app uses a standard mapping from request to controller, based on request URL. The framework class com.interface21.web.servlet.UrlHandlerMapping supports a simple mapping syntax from the incoming URL to the name of a controller bean in the same bean definition, as shown here:

 <bean name="a.urlMap"
 class="com.interface21.web.servlet.UrlHandlerMapping">
 <property >
 /welcome.html=ticketController
 /show.html=ticketController
 /foo/bar.html=otherControllerBeanName
 *=defaultControllerBeanName 
 </property>
 </bean>



The highlighted line specifies a default controller, if no other controller matches. If no default controller is specified, URLs that don't match will result in the controller servlet sending an HTTP 404 Not Found response code. Note that the mappings property of the com.interface21.web.servlet.UrlHandlerMapping class is set to a string value in a format supported by the java.util.Properties class. This is achieved because the com.interface21.beans.BeanWrapperImpl class, discussed in , registers a standard property editor for class java.util.Properties; conversion from String to Properties is automatically performed by the framework. It's not necessary to understand the workings of handler mappings to use the framework. The standard mapping syntax shown above is sufficient for most purposes and does as much as is possible with many frameworks. However, the framework allows custom mappings to be defined simply by implementing the simple HandlerMapping interface, which might choose a controller based on any aspect of the request, such as URL, cookies, user authentication status, or request parameters. It's possible to define any number of mappings as desired for each controller servlet. The controller servlet will find all beans in its app context that implement the HandlerMapping interface, and apply them in alphabetical order by bean name until one finds a match, throwing an exception if none does. Thus the bean name given to mappings is significant, although they are never retrieved by name by app code.

This approach to mapping requests to controllers is more powerful and easier to customize than the approach used in any other framework I'm aware of.

Request Controller (com.interface21.web.servlet.mvc.Controller)

Important 

The com.interface21.web.servlet.mvc.Controller interface defines the contract that app controllers must extend. Implementations of this interface are multithreaded, reusable objects analogous to Struts actions.

A request controller is essentially an extension of the controller servlet's functionality. By delegating to one of a number of controller objects, the controller servlet remains generic and app code is freed from the need for chains of if…else statements. Request controllers normally handle requests to a single URL. Request controllers implement the com.interface21.framework.web.servlet.mvc.Controller interface, which contains a single method:

 ModelAndView handleRequest(HttpServletRequest request,
 HttpServletResponse response)
 throws ServletException, IOException;


The returned ModelAndView object contains a data model (a single object or a java.util.Map) and the name of a view that can render the model. This doesn't introduce a dependency on any view technology: the name is resolved by a ViewResolver object. A null return from a controller's handleRequest() method indicates that the controller generated the response itself by writing to the HttpServletResponse. Controllers, like servlets and Struts actions, are multithreaded components. Therefore any instance data should normally be read-only, lest its state becomes corrupted or the need to synchronize access degrades performance. Controllers, like all apps, objects in this framework, are JavaBeans. This enables their properties to be set in the app context definition associated with the relevant servlet. Bean properties are all set at initialization time, so will be read-only at run time. The following XML element defines the main controller used in the sample app. Note that this controller exposes bean properties that enable the framework to make business objects available to it (in the highlighted lines), as well as configuration properties that enable its behavior to be changed without modifying Java code:

 <bean 
 class="com.wrox.expertj2ee.ticket.web.TicketController">
 <property beanRef="true">
 calendar
 </property>
 <property beanRef="true">
 boxOffice
 </property>
 <property beanRef="true">
 availabilityCheck
 </property>
 <property beanRef="true">
 userValidator
 </property> 
 <property >3.50</property>
 <property >1</property>
 </bean>



We'll look at controller implementations shortly. Although the Controller interface is very simple, the framework provides several abstract implementations offering different workflows, which app controllers can extend.

Note 

The framework can also work with any other controller interface for which a "HandlerAdapter" SPI implementation is provided. This is beyond the scope of the present discussion, but delivers great flexibility.

Models

A controller returns both a model and a view name. The com.interface21.web.servlet.ModelAndView class contains model data and the string name of the view that should render the model. Unlike in Maverick and WebWork, the model data isn't tied to a single object, but is a map. A convenience constructor for the ModelAndView class enables us to return a single, named object like this:

 ModelAndView mv = new ModelAndView ("viewName", "mode1Name", modelObject);


This is analogous to the Maverick single-model approach. The name parameter may be used to add model values to an HttpServletRequest if necessary.

Note 

Why return model data as a Map, instead of simply exposing model data as request attributes? By setting model data as request attributes, we constrain view implementations. For example, an XSLT view would require model data in the form of JavaBeans, not request parameters. A view that forwarded to another system might try to render model values as string parameters. Thus there is a real value in exposing the model without dependence on the Servlet API.

Important 

A model consists of one or more Java objects (usually, JavaBeans). Each model object has an associated name; hence the complete model is returned as a Map. Often this map will have a single entry.

Views

A view is an object that can render a model. The purpose of the View interface is to decouple controller code from view technology by hiding view technology specifics behind a standard interface. Thus this framework achieves the goal of view substitutability that we considered earlier this chapter. A view does not perform any request processing or initiate any business logic: it merely takes the model data it is given and renders it to the response. This formalizes use of the "Service-to-Worker" early data retrieval strategy discussed above. Like controllers, views must be threadsafe, as they execute on behalf of multiple clients. Views are also normally JavaBeans, enabling them to be defined using our consistent property management approach. A view must implement the com.interface21.web.servlet.View interface. The most important method is:

 void render (Map model,
 HttpServletRequest request,
 HttpServletResponse response)


This writes output to the response object, based on model data. A view may use any one of a number of strategies to implement this method. For example:

To understand how this works, let's consider the implementation of the com.interface21.web.servlet.view.InternalResourceView implementation used to forward JSP pages. This takes the JSP's path within the WAR as a bean property. In the render() method, it:

Custom views can easily implement the View interface, or extend the com.interface21.web.servlet.view.AbstractView convenience superclass. For example, in one recent project I implemented a custom view that extracted embedded HTML from a fixed XPath within a model that was guaranteed to be XML, and wrote it to the response. In this situation, this was a simpler and more efficient approach than using a JSP or even an XSLT view.

It's also easy to provide view implementations that use just about any output technology to expose model data. For example, views provided with the framework support radically different output technologies such as XMLC and PDF generation.

ViewResolver

Controllers normally return ModelAndView objects including view names rather than view references, thus decoupling controller from view technology. Unlike Struts view definitions, which may be global but are normally associated with actions, all view definitions are global.

Note 

In my opinion, the notion of view scope adds less value than complexity. View names can include qualifiers such as package paths if necessary to identify them uniquely in an app with many views.

It is possible to construct a ModelAndView object containing an actual view reference in rare cases where it's appropriate to return an anonymous inner class or other instantiated view, rather than a shared view object used by the entire app. A controller could do this as follows:

 View myView = new MySpecialView (params);
 ModelAndView mv = new ModelAndView (myView, "modelName", modelObject);


The way in which the framework resolves view names is configurable through a controller servlet's app context. An implementation of the com.interface21.web.servlet.ViewResolver interface can be set in a controller servlet's context with the predefined bean name viewResolver. If none is specified, a standard implementation, com.interface21.web.servlet.view.ResourceBundleViewResolver, looks on the classpath (that is, under /WEB-INF/classes) for a resource bundle with the default name views.properties, containing definitions of views as beans. This enables transparent support for internationalization using the standard Java ResourceBundle functionality. For example, we could define another file, views_fr.properties or views_fr_ca.properties to provide separate view definitions (perhaps using different JSP pages) for French and French Canadian locales. (The ease of harnessing standard implementation support is the main reason I've chosen to use a properties format, rather than XML, for default view configuration.) It's easy to write a custom view resolver if necessary (it's free to create and return view objects any way it pleases), but the standard implementation meets all normal requirements. Often we want to ensure that each of several controller servlets uses a unique name for its views definition file, by setting the basename parameter of the ResourceBundleViewResolver class as follows:

 <bean 
 class="com.interface21.web.servlet.view.ResourceBundleViewResolver">
 <property >true</property>
 <property ticketServlet</property> 
 </bean>



View definitions for the ResourceBundleViewResolver follow the properties bean definition syntax we've already seen. The following view from the sample app uses a default view class suitable for exposing JSP pages within the WAR (this file is found within the sample app's WAR at /WEB-INF/classes/views.properties):

 welcomeView.class=com.interface21.web.servlet.view.InternalResourceView
 welcomeView.url=/welcome.jsp


The following view uses a standard View implementation that provides XML data on the fly for an XSLT stylesheet. (This is implemented using the same Domify package as used by Maverick.) The root property sets the name of the root element domification should create for the model:

 xView.class=com.interface21.web.servlet.view.xslt.XsltView
 xView.root=myDocument
 xView.stylesheet=/xsl/default.xsl


Important 

As ResourceBundles and properties files are normally loaded from the classpath, they're normally found under /WEB-INF/classes in a WAR.XML-based configuration files are loaded by URL by framework code, and are normally located in the /WEB-INF directory, which is not itself of the classpath. This is true of most web app frameworks.

ContextLoaderServlet

We've covered all the moving parts of the framework. However, there's one vital piece of plumbing left to consider. Before any controller servlet works a root WebappContext object must be attached to the ServletContext. This contains read-only data, so it won't matter that each server in a cluster will have its own instance. The root WebappContext is created and set as a ServletContext attribute by the com.interface21.web.context.ContextLoaderServlet, which must be set to load on startup before any other servlet, using the <load-on-startup> web.xml element. Note that the global WebappContext object isn't merely available to classes in our web app framework; it can be accessed by servlet filters, JSP custom tags, or components implemented using other web frameworks, allowing them to access business objects exposed as JavaBeans. In the sample app, the following element in web.xml defines the ContextLoaderServlet:

 <servlet>
 <servlet-name>config</servlet-name>
 <servlet-class>
 com.interface21.web.context.ContextLoaderServlet
 </servlet-class>
 <init-param>
 <param-name>contextClass</param-name>
 <param-value>
 com.interface21.web.context.support.XmlWebappContext
 </param-value>
 </init-param>
 <load-on-startup>1</load-on-startup> 
 </servlet>



Note that there are no URL mappings onto this servlet: its purpose is to load configuration and make it available to other web components. I could have modeled this object as a Servlet 2.3 app listener. However, there was no reason to break compatibility with Servlet 2.2 merely to do this. Furthermore, should the ContextLoaderServlet ever need to present a web interface - for example, to return information about the root WebappContext - the decision to model it as a servlet will pay off.

Custom Tags

Finally, there are a number of custom tags, used to perform data binding, expose messages with internationalization support, and iterate over models. These are useful bonuses, not central functionality, as JSP is only intended to be one of the view technologies supported by this framework.

We'll discuss some of these custom tags later in this chapter.

Workflow Refinements

Now we understand how the basic MVC workflow is implemented, let's look at the framework's abstract implementations of com.interface21.servlet.mvc.Controller to deliver various different workflows. The following UML class diagram shows the relationship between these implementations, and how app-specific controllers can extend them. The classes above the line are generic framework classes; those below the line are app-specific examples we'll consider shortly.

Java Click To expand

The classes most likely to be extended by app controllers are the following. All use the Template Method design pattern:

We'll see these classes in action in the examples in the next section.

Examples

Let's now look at some simple code examples. Later we'll look at more realistic examples from the sample app. The examples discussed in this section are included in a separate download from the sample app, called i21-web-demo. This download, which includes an Ant build script, can also be used as a skeleton for web apps using this framework.

A Basic Controller Implementation

The following is a complete listing of a simple implementation of the Controller interface, which chooses a view depending on the presence and validity of a "name" request parameter. If the name parameter is present, it attempts to validate it (for demonstration purposes, validation merely checks whether the name contains a hyphen character, which is deemed to be illegal). This controller can forward to any of three views, in the following three scenarios:

 package simple;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import com.interface21.web.servlet.ModelAndView;
 import com.interface21.web.servlet.mvc.Controller;
 public class SimpleController implements Controller {


 public ModelAndView handleRequest (HttpServletRequest request,
 HttpServletResponse response) throws ServletException {
 String name = request.getParameter ("name") ;
 if (name == null || "" .equals (name) ) {
 return new ModelAndView ("enterNameView") ;
 } else if (name.indexOf ("-") != -1) {
 return new ModelAndView("invalidNameView", "name", name) ;
 } else {
 return new ModelAndView("validNameView", "name", name) ;
 }
 }


 }


Often we'll want to extend the com.interface21.web.servlet.mvc.AbstractController class, which provides the following support:

The AbstractController class uses the Template Method design pattern, providing a final implementation of the handleRequest() method that enforces the request method and session checks and forcing subclasses to implement the following method, which is invoked if they succeed:

 protected abstract ModelAndView handleRequestInternal (
 HttpServletRequest request,
 HttpServletResponse response )
 throws ServletException, IOException;


The contract for this method is identical to that for the handleRequest() method from the Controller interface.

A Controller Exposing Bean Properties

Controllers are very easy to parameterize as they're JavaBeans, obtained by the controller from the app bean factory. All we need to do to be able to externalize configuration is to add bean properties to a controller. Note that these bean properties are set once at configuration time, so are read-only when the app services requests, and don't pose threading issues.

Note 

This ability to configure controller instances at startup is a major advantage of using multithreaded, reusable controllers, as opposed to the Maverick and Web Work new controller per request model. Struts, which also uses reusable controllers, also provides the ability to set properties on controllers (actions) in its configuration file, through <set-property> elements nested within <action> elements. However, Struts only supports simple properties; it cannot handle relationships to other framework objects, limiting the ability to "wire up" action objects as part of an integrated app.

Let's consider the following simple controller. It uses a bean property, name (highlighted), which can be set outside Java code to modify the model data output by the controller. I've made this controller extend AbstractController so that it can override the init() lifecycle method to check that its name parameter has been set. This controller sends the user to the enterNameView if they haven't supplied a name parameter. If a name parameter is supplied, the user is sent to a greetingView for which the model is a greeting string greeting the user by name and showing the value of the name property of the controller instance:

 package simple;
 import java.io.IOException;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import com.interface21.context.appContextException;
 import com.interface21.web.servlet.ModelAndView;
 import com.interface21.web.servlet.mvc.AbstractController;
 public class HelloController extends AbstractController {


 private String name;
 public void setName (String name) {
 this.name = name;
 }


 protected ModelAndView handleRequestInternal (
 HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 String pname = request.getParameter ("name") ;
 if (pname == null) {
 return new ModelAndView ("enterNameView") ;
 } else {
 return new ModelAndView ("greetingView" , "greeting" ,
 "Hello" + pname + ", my name is " + this.name) ; 
 }
 }



 protected void init() throws appContextException {
 if (this.name == null)
 throw new appContextException (
 "name property must be set on beans of class " +
 getClass() .getName() ) ;
 }


 }


We can configure this object as follows:

 <bean 
 class="simple.HelloController" >
 <property >The Bean Controller</property> 
 </bean>



Bean properties are often of business interface types, enabling them to be set to other beans in the same app context. We'll see this usage pattern in the sample app. The ability to parameterize controllers so easily, and in a standard way, has many advantages. For example:

A Multi-Action Controller

While using a single controller per request type is often a good model, and has advantages such as the ability to use a common superclass for all controllers, it isn't always appropriate. Sometimes many app controllers end up being trivial, and there's a confusing proliferation of tiny classes. Sometimes many controllers share many configuration properties, and, although concrete inheritance can be used to allow the common properties to be inherited, modeling the controllers as distinct objects is illogical. In such cases, it's more natural for a method, rather than a class, to process an incoming request. This is a pragmatic approach that can improve productivity, but isn't always the best design decision. Struts 1.1 introduces support for method-based mapping with the org.apache.struts.actions.DispatchAction superclass, and I have borrowed the idea in the present framework in the com.interface21.web.servlet.mvc.multiaction.MultiActionController superclass, which implements the Controller interface to invoke methods on subclasses by name for each request. None of the other frameworks I know of supports this feature. Multiple request URLs can be mapped onto a DispatchAction or MultiActionController controller. The only way to achieve method-based functionality is to use reflection to invoke methods by name. This will have a negligible performance overhead if done once per incoming request, and is a good example of the benefits of reflection concealed by a framework. Both the Struts DispatchAction and my MultiActionController cache methods, meaning that the overhead of reflection is minimized. The main challenge is to decide which named method to invoke. The framework superclass can analyze concrete subclasses at startup to identify and cache request handling methods. In the present framework, request handling methods must accept at least HttpServletRequest and HttpServletResponse objects as parameters and must always return a ModelAndView object. Request handling methods can have any name, but must be public, as otherwise it's more difficult to use them reflectively. A typical signature for a request handling method is:

 public ModelAndView meaningfulMethodName (
 HttpServletRequest request, HttpServletResponse response) ;


The Struts DispatchAction determines the name of the request handling method to use from the value of a request parameter, although the abstract LookupDispatchAction subclass defers this choice to concrete subclasses. My MultiActionController is more flexible in that it uses the Strategy design pattern to factor this choice into the following simple interface, an instance of which can be set on any controller that extends MultiActionController as a bean property:

 public interface MethodNameResolver {
 String getHandlerMethodName (HttpServletRequest request)
 throws NoSuchRequestHandlingMethodException;
 }


Note 

The Struts LookupDispatchAction overrides DispatchAction to look up a method name matching a special request parameter in app properties. It seems that the designers of Struts don't believe in interfaces. Putting the varying functionality in an interface (the Strategy design pattern) is far more flexible than subclassing, as it enables us to modify the mapping strategy used by app classes extending MultiActionController without modifying the source code by changing superclass. This is a good example of the superiority of composition over concrete inheritance discussed in .

The default implementation of MethodNameResolver(com.interface21.web.servlet.mvc.multiaction.InternalPathMethodNameResolver) bases the method name on the request URL. For example, /test.html is mapped to a method named test; / foo /bar.html is mapped to a method named foo_bar. However, we will normally want the mapping to be more configurable, as we shouldn't tie controller implementation to mapped URLs. The com.interface21.web.servlet.mvc.multiaction.ParameterMethodNameResolver implementation provides similar behavior to Struts, selecting a method based on the value of an action parameter included with the request. The com.interface21.web.servlet.mvc.multiaction.PropertiesMethodNameResolver implementation is much more configurable. This enables the mapping to be held in properties specified in the controller servlet's XML configuration file: the same mechanism as used by our UrlHandlerMapping implementation. The following bean definition from the sample app demonstrates this approach:

 <bean 
 class="com.interface21.web.servlet.mvc.multiaction.
 PropertiesMethodNameResolver"> 
 <property >
 /welcome.html=displayGenresPage
 /show.html=displayShow
 /bookseats.html=displayBookSeatsForm
 /reservation.html=processSeatSelectionFormSubmission
 /payment.html=displayPaymentForm
 /confirmation.html=processPaymentFormSubmission
 /refresh.html=refreshReferenceData
 </property>
 </bean>



This object is set as the value of the inherited methodNameResolver property on app-specific controllers derived from MultiActionController as follows:

 <bean 
 class="com.wrox.expertj2ee.ticket.web.TicketController" >


 <property 
 beanRef="true">ticketControllerMethodNameResolver
 </property>


 </bean>


Alternatively, an app can easily supply a custom implementation of the MethodNameResolver interface, which may determine method name on custom criteria other than request URL, such as the user's session state. The MultiActionController class is also more sophisticated than the Struts DispatchAction in that it provides the ability to invoke methods on another, delegate object, rather than a subclass. (The delegate can also be set as a bean property.) This is useful if there's need to invoke a named method in a class with a separate inheritance hierarchy. The delegate doesn't need to implement any special interface, just expose public methods with the correct signatures. To illustrate how the "multi action" controller approach works in practice, let's look at some code from the main controller for the sample app. The TicketController class extends MultiActionController, meaning that it doesn't implement the handleRequest() method, which is implemented as a final method by MultiActionController, but must implement a number of request handling methods that will be invoked by name. This single class implements all controller code for the sample app, consolidating trivial methods (that would otherwise consume more space as trivial classes) and exposing configuration bean properties relating to all methods:

 public class TicketController
 extends com.interface21.web.servlet.mvc.multiaction.MultiActionController {


I've omitted the configuration bean properties. However, instance data is available to all methods. The request handling methods must be public, and may throw any type of exception. Otherwise, they fulfill the same contract as handleRequest() methods, returning a ModelAndView object. The following three methods correspond to the first three keys in the properties-based mapping shown above. I've shown the implementation of only one of the methods:

 public ModelAndView displayGenresPage (HttpServletRequest request,
 HttpServletResponse response) {
 List genres = calendar.getCurrentGenres() ;
 return new ModelAndView ("welcomeView" , "genres" , genres) ;
 }
 public ModelAndView displayShow (HttpServletRequest request,
 HttpServletResponse response)
 throws ServletException, NoSuchPerformanceException {
 //Implementation omitted
 }
 public ModelAndView displayBookSeatsForm (HttpServletRequest request,
 HttpServletResponse response)
 throws ServletException, NoSuchPerformanceException {
 //Implementation omitted
 }


The MultiActionController class also offers a simple data binding capability. The final parameter in a request processing method (request and response will always be required) will be assumed to indicate that the method expects a command bean. The type of the command can be determined from the method signature, which will look like the following example from the sample app:

 public ModelAndView processSeatSelectionFormSubmission (
 HttpServletRequest request,
 HttpServletResponse response,
 ReservationRequest reservationRequest) ;


A new command instance will be automatically created using reflection (commands must be JavaBeans with no-argument constructors), and request parameters bound onto its properties. If data binding succeeds (that is, there are no type mismatches), the method will be invoked with the populated command object. Any data binding exceptions will be assumed to be fatal. This approach isn't powerful enough for many data binding apps, but it can be very useful. It's similar to the Struts ActionForm approach if we choose to use effectively untyped string parameters, in which case type mismatches are impossible. We'll examine the use of this data binding mechanism in the sample app near the end of this chapter. The MultiActionController class can also detect when subclass methods require session state parameters. In this case, the user's HttpSession will be passed as a parameter if a session exists. If no session exists, this will be treated as a workflow violation and an exception invoked, without invoking the subclass method. Another feature offered by the MultiActionController lacking in the Struts DispatchAction (or indeed the Struts framework as a whole) is support for "last-modified" HTTP headers to support browser or proxy caching of dynamic content. This support is used in the sample app and is discussed in . This "multi-action" approach has the following advantages, compared with the one-controller-per-request model:

This approach has the following disadvantages:

The choice essentially comes down to the philosophical choice between compile time and run time. A method-based approach is a good choice if it makes an app easier to maintain, and a poor choice if it's used to build huge controllers that group functionality that isn't related and doesn't depend on the same configuration properties. Of course, it's possible to mix the two models: for example, using single classes to handle actions that require more complex control logic, and multi-action classes to handle groups of simple related actions.

Note 

This is by no means a comprehensive tour of this framework's capabilities. See the Interface21 webdemo download for illustrations and explanations of other capabilities.

JaVa
   
Comments