Web Application Frameworks

As the MVC Front Controller pattern is almost universally advocated, there are several implementations available.

Note 

Although it's by far the most popular, MVC isn't the only way to structure web applications. Some products, such as the Tapestry framework (see the overview at http://tapestry.sourceforge.net/doc/api/overview-summary.html), take a different approach to solving the problems that the MVC patterns addresses. However, none has been widely adopted, so I won't discuss them here.

Although it's not particularly difficult to implement a controller framework, you're unlikely to have any reason to. Let's move from pattern theory to practice, looking first at what real frameworks do and then at some implementations.

Common Concepts

Most MVC web application frameworks use the following concepts:

  • A single generic controller servlet for a whole application or part of an application. All application URLs are mapped in the standard web.xml deployment descriptor onto the framework's controller servlet.

  • Proprietary mappings (often defined in an XML file) that enable the controller servlet to choose a delegate to handle each request. Such mappings are also usually based on request URL. Delegates will implement a framework-specific API. I'll refer to such delegates as request controllers below: the controller servlet is effectively a meta-controller.

  • What I call the named view strategy. Views are identified by name, enabling request controllers to specify view names, rather than view implementations when selecting a view. Framework infrastructure will resolve view names. This decouples view implementation and controller. The implementation of a view with a particular name can change without affecting controller code.

Many frameworks add the role of command (discussed above) to those of model, view, and controller. However, these four roles are not necessarily played by separate objects. In Struts and the application framework used for the sample application, the four roles are usually separate, with controllers reused throughout the application lifecycle. In Web Work and Maverick, command, controller, and model are normally the same object, created to handle a single request.

Important 

Choosing a web application framework is a second order issue, compared with committing to an MVC approach: even a poor MVC implementation will do a much better job of separating presentation and business logic than a clever JSP Model 1 implementation. Alternative frameworks share so many common concepts that, having learned one, it's fairly easy to work with others.

Remember that the web tier should be thin. The code that uses a framework shouldn't be complex. If much application code depends on a particular framework, the framework is poorly designed or is being used incorrectly.

Available Frameworks

In this section, I'll briefly survey some leading MVC frameworks.

Struts

The most widely adopted MVC framework is the open source Apache Struts (http://jakarta.apache.org/struts/). Struts was originally written by Craig McClanahan, the main developer of the Tomcat servlet engine, and was released in mid 2000, making it the longest-established open source web application framework. Partly because of its relatively long history, Struts has achieved remarkable buy-in by developers, with many add-ons now available around it. At the time of writing, Struts version 1.1 is in late beta. Hence I'll discuss the capabilities of both version 1.0 and 1.1, but concentrate on 1.1, which contains significant enhancements and should be used in any new projects using Struts.

There are several books and many online articles devoted to Struts, for example Java Web Development With Struts from Manning (ISBN: 1-930110-50-2) so I won't describe it in detail here, but will merely survey how it approaches the concepts we've discussed.

Struts uses a single controller servlet for a whole web application or subset of a web application. Struts 1.0 only allows one controller servlet per application - a serious limitation as it means that the single controller's configuration file will become unmanageably large. Struts 1.1 allows multiple controllers, each with its own configuration file. A Struts controller is of the concrete framework class org.apache.struts.action.ActionServlet. The standard controller servlet is responsible for handling all incoming requests, and is configured by the /WEB-INF/struts-config.xml file. (As I noted above, many frameworks use XML for their configuration.)

As in most frameworks, the controller servlet is generic. It doesn't contain application-specific code, and it isn't usually necessary to subclass it (at least in Struts 1.1; subclassing was often necessary in Struts 1.0). The controller uses its mapping information to choose an action that can handle each request. A Struts action extends the org.apache.struts.action.Action class. The most important method is the following, invoked on each request:

    public ActionForward execute (ActionMapping mapping,             ActionForm form,             HttpServletRequest request,             HttpServletResponse response)       throws Exception; 
Note 

This method was named perform() in Struts 1.0; perform() is deprecated as of Struts 1.1.

A Struts action must be threadsafe: as actions essentially extend the functionality of the controller servlet, the threading issues are exactly the same as in coding a servlet.

Struts documentation advises that actions shouldn't include business logic but "should invoke another object, usually a JavaBean, to perform the actual business logic. This lets the action focus on error handling and control flow, rather than business logic." However, Struts does not provide infrastructure for managing business objects and exposing them to web-tier actions.

The two Struts-specific parameters to the action execute() method (the ActionMapping and ActionForm parameters) convey the core Struts functionality, so let's look at them in more detail.

The ActionMapping parameter contains information about the mapping from the request to the ActionForm instance: most importantly, the definition of "forwards" which enables the ActionForm to return a named view at run time.

The ActionForm parameter is a bean that extends the org.apache.struts.action.ActionForm class to expose bean properties corresponding to request parameters. ActionForm beans are automatically populated by Struts from request parameters. Struts uses the Apache Commons beans manipulation package, which has similar goals to the com.interface21.beans package I discussed in the last chapter. However, the Commons bean package is less sophisticated and, in particular, lacks good error handling.

Struts ActionForms have several peculiarities that we must consider. As all ActionForms must extend the Struts superclass, they all depend on Struts and the Servlet API. This means that they can't be passed to business objects, as business objects shouldn't depend on a particular web application framework or the Servlet API. Secondly, any request parameters that may contain invalid data (such as numeric inputs, for which the user might enter non-numeric data) must be matched by ActionForm properties of type String. The bean manipulation code behind the scenes will attempt to convert string request parameters to the appropriate type, but any exceptions in attempting to set properties will result in the org.apache.struts.util.RequestUtils class failing the request by throwing a ServletException.

Thus a Struts ActionForm is not a true domain object. It's a place to hold user data until it can be validated and transferred into domain objects such as commands. The advantage of this approach is that invalid data can be re-displayed easily; the disadvantage is that we will need to get this data into true domain objects at some point, so Struts has only taken us part of the way towards true data binding.

The need to derive ApplicationForms from a single superclass has always seemed to me a design flaw. Not only does it tie commands to the Struts framework and Servlet API, it incorrectly exposes inherited framework properties to update via data binding from request parameters. For example, adding a servlet parameter with a string value will break just about any page generated by a Struts 1.0 application (with a failure to set a property of type ActionServlet to a string). Struts 1.1 introduces a workaround for this particular problem, but the root of the problem is the whole ActionForm concept.

The ActionForm class also defines a validate() method with the following signature:

    public ActionErrors validate (ActionMapping mapping,                                  HttpServletRequest request); 

Subclasses may override this method to validate the state of an action form after population from request parameters. Struts also offers alternative approaches to validation. We'll discuss validation in general, and the Struts approach to validation, in detail later in this chapter.

Important 

In the Struts model a standard controller servlet (the Struts ActionServlet) delegates to a number of Action objects. Each Action object is given as a parameter an ActionForm object, which contains JavaBean properties pre-populated from the request using reflection. As ActionForm objects are tied the Struts framework, they cannot be used as commands in an application. Each Action object is responsible for initiating the business operations required to handle the request. After processing is complete, each action should return a mapped view or "action forward".

Struts also comes with several JSP tag libraries to handle data binding and other operations. Note that some of these tag libraries are superseded by the new JSP Standard Tag Library (JSTL), discussed in the next chapter.

The key Struts tag libraries are:

  • Bean
    Miscellaneous tags that perform tasks such as: exposing a cookie value as a request variable; defining scripting variables based on header values, request parameters, and bean properties; and invoking another resource within the application and saving the generated output to a string.

  • HTML
    Tags that help build forms for submission to Struts actions. These tags can use a backing bean value to populate form fields, simplifying JSP code. The tags in this library, such as <html:text>, are used in place of HTML input tags: the custom tags expose all the necessary attributes and generate the required HTML. While custom tags are essential to populate some HTML controls such as dropdowns and radio buttons, using Struts controls in place of normal HTML controls can make the HTML harder to understand and more difficult to edit with a GUI tool. I prefer an approach in which tags provide data values, but don't generate markup.

  • Logic
    Conditional tags enabling comparisons and iteration without using scriptlets. JSTL tags are likely to supersede some of these tags.

  • Template
    Tags to establish a common layout that can be parameterized by specifying the content in each section.

Note 

Note that all Struts tags enable access to nested properties. For example, the property path spouse.age passed to a tag will cause an evaluation of getSpouse().getAge(). The JSTL Expression Language, discussed in the next chapter, removes the need for such support in web application frameworks.

There are also various add-on tag libraries available, although not all the tags depend on the Struts framework itself. The availability of a range of associated tag libraries is a major reason for Struts' success.

Despite its popularity, I'm not a big fan of Struts. It's good enough, but far from an ideal J2EE web application framework:

  • The ActionForm approach - central to the Struts request processing model - is poor. Bean binding is primitive, meaning that only string properties are really useful. This adds little value over simply querying request parameters. The idea of copying properties from an action form to a business command is inelegant and there's no support for type checking.

  • Struts is too JSP-oriented, although it is possible to use Struts with other templating technologies.

  • Struts is based almost entirely on concrete classes. This makes it hard to customize Struts' behavior.

  • Although things have improved significantly with version 1.1, the Struts codebase is poor. Not surprisingly, there have been numerous deprecations in moving to 1.1.

Struts 1.1 corrects many (but not all) of the shortcomings of Struts 1.0: for example, by allowing the use of multiple controller servlets in an application. (The mechanism for this isn't very elegant, however - it's clear that it was an afterthought.) Other enhancements in Struts 1.1 include the introduction of the org.apache.struts.actions.DispatchAction superclass, allowing several actions to be performed by the same class. This is very useful in cases where many request types call for simple handling; it avoids the proliferation of many trivial action classes. We'll discuss this concept further below. Struts 1.1 also introduces declarative exception handling: another concept discussed later.

Maverick

Another open source framework is Maverick (http://mav.sourceforge.net), which concentrates on delivering MVC workflow, and doesn't provide presentation support such as tag libraries (the name is a play on MVC). One of the two main Maverick developers, Jeff Schnitzer, is also the author of the JUnitEE test framework, which we discussed in Chapter 3. Maverick is presently in version 2.1, which is discussed here. Version 2.0 was a major revision of the original model. Maverick version 1.0 was released in mid 2001.

Like Struts and most other frameworks, Maverick uses a single controller servlet as the entry point. A Maverick controller servlet is of class org.infohazard.maverick.Dispatcher. All application URLs are mapped onto this servlet in web.xml. The controller servlet takes its configuration from the Maverick-specific /WEB-INF/maverick.xml config file. Maverick currently allows only a single controller servlet - and hence, configuration file - per web application. As with Struts 1.0, this can make configuration hard to manage in large applications.

Note 

It's difficult to provide a brief overview of Maverick, as Maverick is highly configurable (much more so than Struts). The basic workflow can be customized by the user, and Maverick provides several standard classes that web-tier application classes can extend to provide different workflows. The following discussion describes the model offered by Maverick 1.0, and which still appears to be advocated in Maverick 2.0, judging by its use in the "FriendBook" sample application. This model is based on the use of "throwaway" controllers.

Maverick differs from Struts in that a Maverick request controller is usually a JavaBean. In this model, a new "throwaway" controller instance is created to handle each request. (However, Maverick 2.x also supports reusable controllers, as used by Struts.) In this model, Maverick does not separate between controller (analogous to a Struts action) and command (analogous to a Struts ActionForm). This has the advantage that each new controller doesn't need to be threadsafe - it doesn't need to support concurrent invocation. However, it creates a proliferation of controller instances.

A request is handled as follows:

  • A new org.infohazard.maverick.flow.Controller object is created using reflection (the controller must have a no-arg constructor). An application controller normally extends the org.infohazard.maverick.ctl.ThrowawayBean2 superclass provided by the Maverick framework.

  • Bean properties are set on the controller from request parameters using reflection, by the org.infohazard.maverick.ctl.ThrowawayBean2 superclass. The controller bean itself can validate properties that are successfully set; a fatal ServletException will result on any type mismatches (meaning that we need to stick with string properties as with Struts ActionForms to guarantee a chance to validate all input). Bean properties population is performed using the same Apache Commons BeanUtil package used by Struts, although it is possible to use Maverick with alternative bean population strategies.

  • The protected perform() method defined by the org.infohazard.maverick.ctl.ThrowawayBean2 superclass is invoked, and should return the string name of a view defined in the Maverick configuration file after invoking business objects. Each controller has access to an org.infohazard.maverick.flow.ControllerContext object, from which it can obtain the servlet request and response, ServletContext and ServletConfig. However, a controller's populated bean properties should normally contain most of the data needed by the controller to do its work.

When its processing is complete, a controller must set the model object to be displayed by views by invoking the setModel() method on the ControllerContext object. Thus the model is restricted to a single object. To return multiple objects, it's necessary to define a container object that includes all necessary data. The org.infohazard.maverick.ctl.ThrowawayBean2 superclass returns itself as the model, combining two more of the four object roles we discussed earlier.

Each named view must implement the org.infohazard.maverick.flow.View interface, which provides a view technology-agnostic way of passing model data to any view in a consistent way. View resolution is handled by the org.infohazard.maverick.flow.Command class, which invokes controllers. The View interface contains the following single method, which must be implemented to render output given model data:

     public void go (ViewContext vctx) throws ServletException, IOException; 

The ViewContext interface exposes model data via the getModel() method (which returns the model object exposed via the controller), and exposes request and response objects.

Standard implementations of the View interface provided with Maverick include an implementation that adds the model to the request as an attribute before forwarding to a JSP, and a redirect view that passes model data (converted to string form) to an external URL in a GET query string. There is also support for the WebMacro and Velocity template engines. As the view interface is so simple, custom views can easily be implemented.

Maverick's approach to view substitution is simple and elegant, and I've borrowed the concept in my framework, discussed below. The explicit separation of the model promotes good practice: it discourages the haphazard setting of request attributes to provide a messy, JSP-only model.

Probably Maverick's most interesting concept is the transparent "domification" or conversion of JavaBean data to XML on the fly to support the use of XSLT stylesheets. (Jeff Schnitzer is a vigorous advocate of XSLT, rather than JSP, views). Maverick's domification library is now a separate open source offering. We've already discussed the concept of domification in Chapter 10; this approach is surprisingly effective in practice. Maverick also supports a configurable "pipeline" of transformations, which can be useful with XSLT but is less relevant to other view technologies.

Another interesting feature is the use of simple basic dispatcher functionality, on which different workflows can be grafted by extending a variety of superclasses provided with the framework. This allows Maverick to support a wide range of idioms without the need to subclass the controller servlet.

On the negative side, the lack of separation between command and controller in the "throwaway controller" model is a different approach leading to the same problem that Struts action forms encounter: the command is dependent on the Servlet and Maverick APIs. This means that it's difficult to achieve a clean separation between web tier and business interface. For example, we can't validate command data without depending on the Maverick API, making it hard to implement validation that isn't web-specific. The creation of a new controller instance to handle each request also makes it hard to parameterize controllers; a single reusable object can expose as many configuration properties as required to be set once only.

Important 

Maverick is a capable alternative to Struts. Maverick is typically used to create a new controller to handle each request (although Maverick supports other models). Controllers are JavaBeans, and the framework transparently sets bean properties behind the scenes. Maverick is relatively easy to use (although the Maverick 2.x model is much more complex than Maverick 1.0), and has built-in support for transparent generation of XML nodes from JavaBean model.

WebWork

A more recent framework is WebWork, another open source offering, designed by distinguished J2EE developer Rickard Oberg, who has contributed to JBoss and other projects. WebWork 1.0 (the version discussed here) was released in mid 2002 (http://opensymphony.com/webwork/)

WebWork, despite its name, isn't purely a web application framework. It adopts a clever approach that minimizes the dependency of application code on web concepts.

WebWork is based on the Command design pattern. Unlike a Struts action, which is a reusable, threadsafe object analogous to a servlet, a WebWork action is a command, created to handle a specific request (the same basic approach as in Maverick). An action is a JavaBean, enabling its properties to be set automatically. A WebWork action must implement the webwork.action.Action interface, which contains the following method:

    public String execute() throws Exception; 

The implicit inputs are the state of the action's bean properties. The return value is a string representing the result - usually the name of the view that should be used to render output. The data outputs (the model) are the state of action bean properties after the execute() method has been called. Thus WebWork combines the role of command, controller, and model in each action object.

Control flow works as follows:

  • Incoming requests are handled by the main dispatcher servlet, webwork.dispatcher.ServletDispatcher (note that this isn't the only way of invoking WebWork actions: different control flows are possible).

  • A new action instance is created. It's possible to implement and set a custom "action factory" to create actions. The default factory uses Class.newInstance().

  • The command's bean properties are populated. In a web application, this population will occur from request parameters; however, the basic approach should work with any interface. (The way in which this is done is rather complicated, through a chain of ActionFactory implementations. The webwork.action.factory.ParametersActionFactoryProxy handles bean property setting.) Type mismatches during property setting can be recorded by WebWork for redisplay; this ability to save such exceptions is a significant improvement on the relatively primitive bean property population of Struts and Maverick.

  • The command's "context" is set. A WebWork context - of type webwork.action.ActionContext - is associated with the current thread. In a web interface, an action can access request parameters and ServletContext through its context. However, this is discouraged as it means that such actions can only be used in a web interface. Thus WebWork actions are not necessarily dependent on the Servlet API (although the ActionContext class is - it would have been better to make only a web-specific subclass of ActionContext dependent on the Servlet API). An action doesn't need to know its context, although it can find it by invoking the static webwork.action.ActionContext.getContext() method. The way in which a context is provided at invocation time is analogous to the provision of context objects to EJB instances.

  • The action's execute() method is invoked. Any exception thrown will be treated as a system exception (resulting in a ServletException), except for the special case of webwork.action.ResultException, which seems intended to provide an alternative return value.

  • The return value of the execute() method is the name of a view that will render the result. The action is made available to the view, for which it provides the model.

Like Struts, WebWork comes with its own JSP tag libraries. However, it's less closely tied to JSP than Struts. There's also support for the Velocity template engine (discussed in the next chapter), and WebWork offers an equivalent to Maverick's XML domification functionality.

WebWork is more elegantly designed and implemented than Struts. Its workflow is more similar to that of Maverick. The ActionContext idea is original and clever. WebWork helps to minimize dependence of application code on the Servlet API and provides more incentive towards good design. However, its negatives include:

  • The creation of an action on every request may be overkill, when there isn't a lot of request data. As with Maverick, it's harder to configure many new actions than one reusable action. It also may be harder for new objects to access business objects.

  • WebWork imposes the Command design pattern on every user interaction, whether or not it's appropriate.

  • Exception handling faces one of the classic problems of the Command design pattern: we don't know what types of exception a particular command may throw. Thus the execute() method is forced to throw Exception.

  • The separation of action from web-tier concepts may not always be realistic or worthwhile. Imposing an abstraction based on the request-response web paradigm unduly constrains the more sophisticated interaction possible with traditional GUIs such as Swing.

Important 

The WebWork framework is based on the Command design pattern, resulting in a basic model more similar to that of Maverick than Struts. WebWork tries to model user actions as interface-agnostic commands, without dependency on the Servlet API.



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