[ Team LiB ] Previous Section Next Section

3.2 Application Structure

It's never easy to start with a clean slate. Whether your application is big or small, the decisions you make at the beginning of the design process affect the application's entire lifetime. Wouldn't it be nice if there was a design pattern to define the overall shape of the presentation tier? The Model-View-Controller (MVC) pattern is just such a pattern.

3.2.1 The Model-View-Controller Pattern

As its name suggests, MVC breaks the problem of user interfaces into three distinct pieces: model, view, and controller. The model stores the application's state. A view interprets data in the model and presents it to the user. Finally, the controller processes user input, and either updates the model or displays a new view. By carefully dividing labor and controlling communication between these three pieces, we can achieve a robust, extensible architecture for the user interface and the application as a whole.

Figure 3-2 gives an overview of the communications within the MVC architecture. While MVC was originally designed for graphical environments梚n which the user acts directly via a mouse or keyboard梠ver time, it has been adapted for use in other areas of programming.

Figure 3-2. Overview of the MVC pattern
figs/j2ee_0302.gif

The MVC paradigm extends quite naturally to enterprise software, where the "user" may be a web browser or web server. Since the presentation tier is request-driven, the "user" can be any request originator. A controller (for example, a web server) handles the request. The model, then, is the business data, and the view is the response that is finally generated. An MVC application is made up of a set of models, views, and controllers that handle related requests.

A controller is the first point of contact for a request. Its job is to coordinate request handling, turning user input into model updates and views. The controller acts as a supervisor, planning what changes need to be made and what view needs to be shown, then calling the chosen model and view to execute the actual plan. An application may have multiple controllers, each responsible for a certain area of the application. By coordinating the response to the user's requests, controllers manage the overall flow of the application.

The model stores application state. Application state is data stored anywhere: databases, JavaBeans, files, network services, or just in memory. The model's job is to manage access to this state, providing the controller and view with a uniform interface. The model does not, however, simply copy data obtained from other sources. It is an abstraction of the data, and may implement and enforce rules on how data is accessed or combine multiple fields of data into a single logical field. Since multiple views and controllers access the model at once, the model might also be aware of transaction and threading issues. The model is a good place to spend some design time, and we cover it in depth in Chapters 6 through 10.

The view reads data from the model and uses the data to generate a response. The view itself is a stateless component that simply transforms values from the model into a format that clients will find useful. Of course, just because its job is simple doesn't mean the view isn't complicated. As we will see in Chapter 4, a view is often made up of a number of sub-views, each of which transforms different data in the model.

The key to the MVC pattern is that each component is simple and self-contained, with well-defined interfaces. Because of these interfaces, components can vary independently and are therefore easier to share and reuse.

3.2.2 Using MVC in J2EE

To start thinking about J2EE in terms of the MVC pattern, let's walk through a standard J2EE interaction. Imagine a web page that allows a user to sign up for a mailing list. The user enters her first name, last name, and email address into a form. When she clicks Submit, our application adds her address to the list and reports whether the submission was successful. Since the form itself never changes, it is stored as a normal HTML file, as shown in Example 3-1. To handle the form submission, we will build a simple application, dividing the presentation into model, view, and controller.

Example 3-1. subscribe.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
  <HEAD>
    <TITLE>Subscribe!</TITLE>
  </HEAD>
  <BODY>
    <FORM action="/servlets/ListController" method="get">
        First Name: <INPUT type="text" name="first"> <br>
        Last Name: <INPUT type="text" name="last"> <br>
        Email Address: <INPUT type="text" name="email"> <br>
        <INPUT type="submit" name="Subscribe!">
    </FORM>
  </BODY>
</HTML>

To some degree, J2EE has MVC concepts built-in. We generally build the presentation tier from three main components: servlets, JSP, and JavaBeans. The model梚n the form of JavaBeans and Enterprise JavaBeans梡rovides access to the data in the business tier. The controller is generally some set of servlets and supporting classes that process input and control navigation. Finally, the view is usually implemented as JSP pages and static HTML. Unfortunately, in the J2EE context, the line between view and controller is easy to blur, as we will see when we discuss presentation tier antipatterns in Chapter 12. Figure 3-3 shows how the MVC components can be used to respond to a request.

Figure 3-3. MVC interactions in J2EE
figs/j2ee_0303.gif

In the next sections, we discuss the JavaBean data model, servlet controller, and JSP view in depth. For readers unfamiliar with the J2EE presentation tier, it's a quick tutorial.

3.2.2.1 The data model

Before we can define the views and controllers that operate on the data model, we must know something about the model itself. First and foremost, the model is a part of the business tier, and it represents the interface between the underlying business data and all possible presentations. The design of the model is therefore datacentric, since it is based on the underlying data and not necessarily the needs of a specific presentation.

In the J2EE world, models are generally JavaBeans, or the similarly named but functionally distinct Enterprise JavaBeans. Like MVC, JavaBeans were originally used in GUIs, but were adopted later for more general use. They provide a generic individual component for use within a larger framework. A JavaBean contains a set of properties, and the JavaBeans specification provides rules about the types and names used for setting and retrieving these properties. The framework, knowing only those rules, can interact with the data the bean represents. The bean itself is responsible for the details of retrieving the information, and can abstract the grubby specifics away from the application.

The rules for writing a JavaBean are intentionally loose: a bean must provide a constructor that takes no arguments; it must not have any public variables; and any access to bean state should take place through getXXX( ) and setXXX( ) methods. Note that there are no requirements to subclass a certain class or implement a particular interface and no restrictions on providing additional methods or constructors. In fact, the requirements to not have public variables and to provide accessor methods are generally good programming practice anyway, and existing classes can often be converted to JavaBeans by simply renaming the getter and setter methods.

For our purposes, we divide a model object into two parts: accessor methods are the getter and setter methods used to change the underlying model, such as the methods to add items to a shopping cart; business methods are other methods that operate on the model data, such as the method to calculate sales tax or process a purchase. Conveniently, any object that can be described this way also fits the definition of a JavaBean, so we will generally use the terms model object and bean interchangeably.

We are ignoring the problem of finding and instantiating our beans梟o easy task, especially where EJBs are concerned. We come back to this issue when we talk about the controller, and in Chapter 9. For now, we assume our model can just as easily be a simple front end to a database, an EJB, or an adapter for a legacy application.

For this example, we will use a very simple bean as the model. Our MailingBean stores all the data about a user needed to subscribe to a particular mailing list. This means storing the user's first name, last name, and email address. These fields are available through six accessor methods: getFirst( ), setFirst( ), getLast( ), setLast( ), getEmail( ), and setEmail( ). The bean also contains a single business method, doSubscribe( ). When this method is invoked, the bean attempts to add the address specified in the email field to the list. If it fails, it will return false and provide a more detailed error message, accessible via the getErrorString( ) accessor. The interface to our bean is shown in Example 3-2.

Example 3-2. MailingBean interface
public interface MailingBean {
    // first name
    public String getFirst(  );
    public void setFirst(String first);
    
    // last name
    public String getLast(  );
    public void setLast(String last);
   
    // email address
    public String getEmail(  );
    public void setEmail(String email);

    // business method
    public boolean doSubscribe(  );
    
    // subscription result
    public String getErrorString(  );
    
}

We're not going to discuss how the bean actually works, since we spend plenty of time in later chapters describing the many methods for connecting to and using business data. For now, it is sufficient to know that we can get a new instance of a MailingBean by calling MailingBeanFactory.newInstance( ).

3.2.2.2 The controller servlet

Now that we have our data model set, we can move on to the controller. Imagine a user fills out the form from Example 3-1 and clicks Submit. In the first step, the user's web browser generates an HTTP GET request for /servlets/ListController, with the data filled into the query string. The request URL looks like:

http://my.server.com/servlets/ListController?first=Jay&last=Test& email=jay%40test.com

Since this is a user interaction, we would like the request to go to a controller in order to coordinate a response.

In J2EE, the controller should be implemented as a servlet. While it is technically possible to use a JSP page as a controller, the programming freedom offered by servlets provides a better match.[1] There are many possible designs for this servlet, some of which we will explore when we talk about the Front Controller and Service to Worker patterns. From our current perspective, the job of the controller is to:

[1] Of course, this can be an arbitrary distinction. Since a JSP is compiled into a servlet before execution, we can write identical code that would be contained in any given servlet as a JSP page. However, we try to avoid this approach, since it makes the control logic too fragile: changes can be made by corrupting a single file with a text editor, rather than requiring a rebuild of Java classes.

  1. Read the request.

  2. Coordinate access to the model.

  3. Store model information for use by the view.

  4. Pass control to the view.

The servlet's doGet( ) method provides us all the information we need to perform these tasks. We can retrieve information from the request, including the first, last, and email parameters submitted via the form. Additionally, we can locate or create JavaBeans for our model using the appropriate API, such as JNDI for EJBs. Once the beans are located, we can update the model using their accessor and business methods. We can also make the beans available to the view. Finally, when we're finished, we can transfer control to the view.

For the first step梤eading the request梩he servlet API provides us with an HttpServletRequest object. The servlet container creates this object based on data from the web server. When a servlet's doGet( ) method is called, it is passed an instance of HttpServletRequest. One of the main functions of this object is to make the request parameters available to the servlet. To retrieve a parameter, we use the getParameter( ) method, passing it in the name of the field as defined in the HTML form. In order to read the email address parameter, we use the code:

String email = request.getParameter("email");

The second step requires working with the model. The exact methods of interacting with the model vary based on the details of the model and what exactly the servlet is trying to do. When working with a JavaBean based model, the servlet typically creates a new bean using a factory, or locates an existing one via a lookup service like JNDI. In this case, we will use the previously mentioned factory:

MailingBean mb = MailingBeanFactory.newInstance(  );

Once the bean exists, it can be manipulated. Here, this means setting the values of various fields and then calling business methods to interact with the actual mailing list software. For example:

mb.setEmail(email);

The third step is to store model information for use by the view. To do this, we need to communicate data between our controller servlet and the view, a JSP page (see the sidebar, "Servlet/JSP Communication"). In this example, we simply store a reference to our MailingBean in the request scope, so it can be manipulated by the view. To do this, we use another method of the HttpServletRequest object, setAttribute( ):

request.setAttribute("mailingbean", mb);

Along with the bean itself, we use a string key. This key is important, because it will be used by the view to identify the bean in question.

Servlet/JSP Communication

Most J2EE applications make extensive use of servlets and Java Server Pages, dividing functionality between the two technologies. In general, we will try to avoid putting code into JSP, instead developing servlets, JavaBeans, and JSP custom tags where significant logic is needed. In order to do this, we will frequently need to exchange data between the different components.

The J2EE environment supports a generic scoping mechanism, allowing different pieces of an application to interchange data. Conceptually, each scope works like a simple hash table, in which objects can be inserted and retrieved along with a string key.

J2EE provides four separate scopes:

Application scope

Shared between all servlets, JSP pages, and custom tags within a J2EE application or within the whole container if no applications are defined. These values are only reset when the J2EE container is stopped or the application is unloaded. The programmatic interface to the application scope is the ServletContext object.

Session scope

Stores information for the life of an HTTP session. There is a separate session object per user, and sessions generally persist for the life of a user's interaction with the system or until they are manually reset. Session scope is accessed via an HttpSession object. It's worth noting that there's nothing in HTTP itself about keeping track of a user's session; the Servlet API implementation builds on top of the HTTP standard to provide this functionality. Session scope is often overused, leading to the Overstuffed Session and Leak Collection antipatterns (described in Chapter 12).

Request scope

Used to store data that only persists for the life of a request, and is removed once the response is sent. Request scope data is stored as attributes of the ServletRequest object, so objects stored in request scope survive through calls to include and forward.

Page scope

Used in JSPs and custom tags. Data stored in the page scope is available for the entire processing of a single JSP page, including all its custom tags. Data in page scope can be treated as a local variable.

Storing a bean in application scope is appropriate if the value is common to all servlets, and also for storing data for longer than one HTTP session. Examples in which application scope is useful include storing global configuration information that can be changed for the whole site at once, or storing data about how many times users have logged in over the life of the application. Session scope is a good place to store user information, such as preferences. Request scope is ideal for sending data that may change from request to request, particularly for transmitting data that is only needed to generate the next view.

Once control passes to the view, the controller has done its job. We transfer control using the servlet's RequestDispatcher object, which is used to forward requests within the server. Using the RequestDispatcher, we can send requests to multiple servlets, JSP pages, or even static HTML pages without the client's involvement. We simply provide a URL to the request dispatcher, and use the forward( ) method to transfer control :

RequestDispatcher dispatcher = 
    getServletContext(  ).getRequestDispatcher("/success.jsp");
dispatcher.forward(request, response);

Our complete ListController servlet is shown in Example 3-3. It performs the steps described above; additionally, it chooses a view based on the results of the MailingBean's business method.

Example 3-3. The ListController servlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;

public class ListController extends HttpServlet {
    public static final String FIRST_PARAM = "first";
    public static final String LAST_PARAM = "last";
    public static final String EMAIL_PARAM = "email";
    public static final String MAILINGBEAN_ATTR = "mailingbean";
    
    public void init(ServletConfig config)
    throws ServletException {
        super.init(config);
    }
    
    public void destroy(  ) {
    }
    
    // handle get requests    
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
    throws ServletException, IOException {
        // read the paremeters from the request
        String first = request.getParameter(FIRST_PARAM);
        String last = request.getParameter(LAST_PARAM);
        String email = request.getParameter(EMAIL_PARAM);
        
        // get the mailing list bean for this list
        MailingBean mb = MailingBeanFactory.newInstance(  );
       
        // set the parameters into the bean
        mb.setFirst(first);
        mb.setLast(last);
        mb.setEmail(email);
       
        // store a copy of the bean in the request context
        request.setAttribute(MAILINGBEAN_ATTR, mb);
        
        // perform the business method
        boolean result = mb.doSubscribe(  );
      
        // choose a page based on the result
        String nextPage = "/success.jsp";
        if (!result) nextPage = "/failure.jsp";
       
        // transfer control to the selected view
        RequestDispatcher dispatcher = 
           getServletContext(  ).getRequestDispatcher(nextPage);
        dispatcher.forward(request, response);        
    }
}
3.2.2.3 JSP: The view

Once the controller has finished actively processing the request, we turn things over to the view. For a web-based presentation tier, the view is anything that writes to the HTTP response. It can be a servlet, a JSP, or even a regular HTML file. We will focus on JSP pages, since they map very well to our idea of a view: they have just enough logic to turn data stored in a JavaBean into an HTML display. Using a servlet as a view tends to create a maintenance nightmare, since each change requires a recompilation and redeployment.

The view, as we've mentioned, is stateless. Each time it is called, it must read the model data and format it as an HTML page. Our JSP page consists of normal HTML with various JSP directives interspersed. The JSP is automatically compiled into a servlet, which generates the response from a combination of the static HTML in the file and the results of processing the various JSP directives.

The view must be able to read its data from the model. Since the controller has already stored our model as a JavaBean in request scope, retrieving the model can be done with a single JSP directive:

<jsp:useBean id="mailingbean" scope="request" 
             class="mvcexample.model.MailingBean" />

This simple directive looks in the current request for an instance of class MailingBean with key "mailingbean". Since this key matches the bean that was added by the controller, our bean should be found. It is now available with the ID "mailingbean" to the jsp:getProperty and jsp:setProperty directives. In order to dynamically include the user's email address, we can use the directive:

<jsp:getProperty name="mailingbean" property="email"/>

Note that the spelling and capitalization of the property element must match the spelling and capitalization of the bean method exactly, with the first letter in lowercase.

Our JSP page will simply generate a text message based on whether the MailingBean's business methods succeed or fail. When the request fails梖or instance, because the email address is not valid梩he JSP page we will use looks like the one in Example 3-4. We will use a similar one for success. While it would be quite easy to provide a JSP page that handles both success and failure, we have chosen to preserve the role of the controller by allowing it to choose the output page.

Example 3-4. failure.jsp JSP page
<%@page contentType="text/html"%>
<jsp:useBean id="mailingbean" scope="request" 
             class="MailingBean" />
<html>
<head><title>Subscription Results</title></head>
<body>
<br><br>
Dear <jsp:getProperty name="mailingbean" property="first"/>,
<br><br>
We're sorry, the address
<jsp:getProperty name="mailingbean" property="email"/>
could not be added to the list.<br><br>
The problem was: 
<jsp:getProperty name="mailingbean" property="errorString"/>.
</body>
</html>

Using the Model-View-Controller pattern provides an overall structure for web applications in J2EE. While the idea of using JavaBeans as a model, JSP for the views, and servlets for the controller is not a requirement, it is a good rule of thumb. It's far more important to understand how the separation between the model, view, and controller makes an application extensible and maintainable.

In Chapter 12, we discuss the Magic Servlet and Compound JSP antipatterns, which should help you recognize when the MVC separation is not being maintained and learn how to fix it.

    [ Team LiB ] Previous Section Next Section