[ Team LiB ] |
3.2 Application StructureIt'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 PatternAs 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—in which the user acts directly via a mouse or keyboard—over time, it has been adapted for use in other areas of programming. Figure 3-2. Overview of the MVC patternThe 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 J2EETo 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—in the form of JavaBeans and Enterprise JavaBeans—provides 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 J2EEIn 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 modelBefore 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—no 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 interfacepublic 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 servletNow 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:
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—reading the request—the 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.
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 servletimport 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 viewOnce 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—for instance, because the email address is not valid—the 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.
|
[ Team LiB ] |