The Web Application Framework Used in the Sample Application

Hence, for the sample application, I've chosen to use my own web application framework - more precisely, the most recent of several web application frameworks I have written.

I considered carefully whether to use an existing framework such as Struts or Maverick. However, I decided that integrated application 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 application frameworks goes back a long way. I developed my first production web application 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 applications.

Important 

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

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

Design Goals

Starting from the basis of the JavaBeans-based infrastructure we discussed in Chapter 11, implementing a web application framework was surprisingly easy.

I adopted the following design goals:

  • The framework should be JavaBeans-based and closely integrated with the infrastructure I've described in the previous chapters:

  • All framework objects, such as controllers and views, should themselves be JavaBeans, allowing for easy and consistent configuration (including the configuration of relationships with application objects).

  • All application objects, whether web processing components or business objects, should be JavaBeans, configurable in the same way by the application context, and with access to all other objects in the same application context. This means that framework definitions can be held in a variety of formats if necessary, not just XML.

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

  • The framework should explicitly separate the roles of controller, model, and view and - where applicable - command.

  • Application code built using the framework should have as little dependence as possible on the Servlet API. For example, while controllers will normally depend on the Servlet API, models should not.

  • The framework should be extensible, through loose coupling throughout the framework based on the use of interfaces, rather than concrete or abstract classes, and consistent use of JavaBeans. For example:

    • The framework should work easily with any view technology. It should not be JSP-centric.

    • The framework should support any mapping strategy from request to logical resource (including custom mappings). This makes it possible to adopt mapping strategies other than the URL-based mapping strategies most frameworks enforce.

  • The framework should completely decouple controllers from views, achieving complete view substitutability (the ability to swap one view for another without affecting any application code).

  • The framework should provide standard view implementations for JSP, XSLT, Velocity, and WebMacro templating technologies, and XMLC (these and other view technologies are discussed in the next chapter). It should be possible to implement support for any view technology easily.

  • The framework should have a clean, maintainable implementation, meeting the code quality, and OO design standards we discussed in Chapter 4.

  • The size of the codebase should be kept small by avoiding irrelevant capabilities. Struts, for example, provides a JDBC connection pool. As a connection pool must be provided by a J2EE application server, this is redundant.

  • The framework should provide a simple MVC implementation, on which different workflows can be layered.

  • The framework should support internationalization.

  • The framework should allow the generation of HTTP cache-control headers at an individual page level, to allow for an important performance optimization. None of the frameworks we've discussed allows this. This important topic is discussed in Chapter 15.

  • The framework should provide custom superclasses to simplify the implementation of typical usage patterns.

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 application 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 application context. It's possible to have multiple controller servlets, each with a separate configuration file. These will share a root WebApplicationContext, but be otherwise independent. Each application-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:

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 application 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 application-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 application framework integrates with the application framework discussed in the last chapter. It also shows how the ControllerServlet is derived from framework superclasses that provide integration with the root WebApplicationContext.

This diagram is a lot to take in one go. It's best used as a reference as you read the following discussion:

click to expand

The com.interface21.web.servlet.mvc.Controller interface, circled in the diagram, is implemented by application-specific controllers. In most applications, 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 application 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 application 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 application-specific request controller to handle each request and to delegate request processing to it.

A web application may have multiple controller servlets, each associated with a WebApplicationContext (an extension of ApplicationContext specific to web applications), although the sample application uses a single controller. All controller servlets in a web application share a common parent application context, enabling them to share as much or as little configuration as they wish. This allows large applications 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 WebApplicationContext (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 application, the context for the single controller servlet instance must be located at /WEB-INF/ticket-servlet.xml.

The controller servlet for the sample application 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 application 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 application 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"          >          <property name="mappings">                /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 Chapter 11, 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 application 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 application 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 application 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 application code is freed from the need for chains of ifelse 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 applications, objects in this framework, are JavaBeans. This enables their properties to be set in the application 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 application. 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 name="ticketController"          >          <property name="calendar" beanRef="true">                calendar          </property>          <property name="boxOffice" beanRef="true">                boxOffice          </property>          <property name="availabilityCheck" beanRef="true">                availabilityCheck          </property>          <property name="userValidator" beanRef="true">                userValidator          </property>                                                                                                                 <property name="bookingFee">3.50</property>          <property name="minutesToHoldReservation">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 application 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:

  • Forwarding to a resource such as a JSP, as in the standard com.interface21.web.servlet.view.InternalResourceView implementation.

  • Performing an XSL transform. The standard com.interface21.web.servlet.view.xslt.XsltView implementation performs an XSL Transform, using a precompiled stylesheet.

  • Using a custom output generation library to generate PDF or a graphics format.

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:

  • Creates a request attribute for each "static value" defined in the view. Views can expose static attributes that are configured at view definition time, and don't vary at run time, although they can be overridden by dynamic model attributes.

  • Creates a request attribute for each value in the dynamic model supplied by the controller. The attribute name is the map key in each case.

  • Forwards to the JSP view, using a request dispatcher. In this model, the JSP will consist largely of template data, but can generate dynamic content if necessary.

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 application 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 application. 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 application 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 name="viewResolver"          >          <property name="cache">true</property>          <property name="basename"ticketServlet</property>                                                                     </bean> 

View definitions for the ResourceBundleViewResolver follow the properties bean definition syntax we've already seen. The following view from the sample application uses a default view class suitable for exposing JSP pages within the WAR (this file is found within the sample application'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 application 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 WebApplicationContext 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 WebApplicationContext 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 WebApplicationContext object isn't merely available to classes in our web application 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 application, 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.XmlWebApplicationContext                </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 application 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 WebApplicationContext - 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 application-specific controllers can extend them. The classes above the line are generic framework classes; those below the line are application-specific examples we'll consider shortly.

click to expand

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

  • com.interface21.web.servlet.mvc.AbstractController
    Simple implementation of a controller that provides a logger object and other helper functionality.

  • com.interface21.web.servlet.mvc.AbstractCommandController
    Controller that automatically creates a new instance of a command class and tries to populate it with request parameters, passing command, and any errors, to a protected template method to be implemented by subclasses. This is similar to the Struts Action contract, although it can use a domain object that does not depend on the web framework as a command.

  • com.interface21.web.servlet.mvc.FormController
    Controller that displays a form bound to a Java object, and can automatically redisplay the form in the event of validation errors. We'll discuss this class under Processing User Input below.

  • com.interface21.web.servlet.mvc.multiaction.MultiActionController
    Controller that allows subclasses to handle multiple request types by mapping each request type to a method, rather than an object.

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 application.

The examples discussed in this section are included in a separate download from the sample application, called i21-web-demo. This download, which includes an Ant build script, can also be used as a skeleton for web applications 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:

  • No name supplied (enterNameView)

  • Name present but invalid (invalidNameView)

  • Name present and valid (validNameView)

    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:

  • Creating a protected Logger instance variable, available to subclasses.

  • Implementing the com.interface21.context.ApplicationContextAware interface to ensure that it is given a reference to the current servlet's WebApplicationContext object, which it saves to expose to subclasses.

  • Exposing a bean property that specifies which request methods are allowed. For example, this makes it possible to disallow GET requests.

  • Exposing a bean property that specifies whether the invocation of the controller should only succeed if the user already has session state.

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 application 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 application 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 application.

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.ApplicationContextException;    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 ApplicationContextException {      if (this.name == null)        throw new ApplicationContextException (          "name property must be set on beans of class " +          getClass() .getName() ) ;      } 

    } 

We can configure this object as follows:

    <bean name="helloController"           >          <property name="name">The Bean Controller</property>                                                                  </bean> 

Bean properties are often of business interface types, enabling them to be set to other beans in the same application context. We'll see this usage pattern in the sample application.

The ability to parameterize controllers so easily, and in a standard way, has many advantages. For example:

  • We never need to write code in web tier controllers to look up configuration.

  • We never need to use the Singleton design pattern or call factory methods: we can simply expose a property of a business interface type and rely on external configuration to initialize it.

  • We can often use different object instances of the same controller class to meet different requirements.

  • It's easy to test controller beans outside a web container. We can simply set their bean properties and invoke their handleRequest() methods with test request and response objects, as discussed in Chapter 3. Such a simple testing strategy won't work if controllers need to perform complex lookups (such as JNDI lookups) at run time.

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 application 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 application 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 application 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 Chapter 4.

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 application demonstrates this approach:

    <bean name="ticketControllerMethodNameResolver"          >                                                                                      <property name="mappings">                /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 application-specific controllers derived from MultiActionController as follows:

    <bean name="ticketController"           > 

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

    </bean> 

Alternatively, an application 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 application. 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 application, 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 application:

    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 applications, 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 application 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 application and is discussed in Chapter 15.

This "multi-action" approach has the following advantages, compared with the one-controller-per-request model:

  • It may be easier to maintain, as there are only a few larger classes instead of many tiny classes

  • It is a more intuitive model in some cases

  • It is easier to configure a few more complex classes than many trivial classes, so application configuration is likely to be simpler

This approach has the following disadvantages:

  • An alternative model to learn: the model of one controller per request type is more familiar, as it's long established by the Servlet specification as well as frameworks such as Struts 1.0. (However, this may change as Struts 1.1 comes into use.)

  • We lose the advantage of compile-time checking. It's possible that subclasses contain invalid method definitions, which we'll only learn about when the application starts up, and the MultiActionController superclass fails to find the necessary request handling methods. However, the MultiActionController implementation provides good error information about probable errors in subclass method signatures.

  • By using individual methods, rather than objects, we lose the ability to catch exceptions in a common superclass and eliminate common request handling code. Exception handling becomes more problematic. When we look at the sample application's implementation below, we'll see how the MultiActionController class enables subclasses to catch exceptions thrown from request handling methods.

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 application 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.



Expert One-on-One J2EE Design and Development
Microsoft Office PowerPoint 2007 On Demand
ISBN: B0085SG5O4
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net