Você está na página 1de 14

This story appeared on JavaWorld at

http://www.javaworld.com/javaworld/jw-12-1999/jw-12-ssj-jspmvc.html

Understanding JavaServer Pages Model 2


architecture
Exploring the MVC design pattern

By Govind Seshadri, JavaWorld.com, 12/29/99

Despite its relatively recent introduction, JavaServer Pages (JSP) technology is well
on its way to becoming the preeminent Java technology for building applications that
serve dynamic Web content. Java developers love JSP for myriad reasons. Some like
the fact that it brings the "write once, run anywhere" paradigm to interactive Web
pages; others appreciate the fact that it is fairly simple to learn and lets them wield
Java as a server-side scripting language. But most concur on one thing -- the biggest
advantage of using JSP is that it helps effectively separate presentation from content.
In this article, I provide an in-depth look at how you can gain optimal separation of
presentation from content by using the JSP Model 2 architecture. This model can also
be seen as a server-side implementation of the popular Model-View-Controller
(MVC) design pattern. Please note that you should be familiar with the basics of JSP
and servlet programming before continuing on, as I do not address any syntax issues
in this article.

Server-Side Java: Read the whole series!

 Welcome to the server-side Java series


 Create forward-compatible beans in EJB, Part 1
 Understanding JavaServer Pages Model 2 Architecture

So, what's wrong with servlets?

While JSP may be great for serving up dynamic Web content and separating content
from presentation, some may still wonder why servlets should be cast aside for JSP.
The utility of servlets is not in question. They are excellent for server-side processing,
and, with their significant installed base, are here to stay. In fact, architecturally
speaking, you can view JSP as a high-level abstraction of servlets that is implemented
as an extension of the Servlet 2.1 API. Still, you shouldn't use servlets
indiscriminately; they may not be appropriate for everyone. For instance, while page
designers can easily write a JSP page using conventional HTML or XML tools,
servlets are more suited for back-end developers because they are often written using
an IDE -- a process that generally requires a higher level of programming expertise.
When deploying servlets, even developers have to be careful and ensure that there is
no tight coupling between presentation and content. You can usually do this by adding
a third-party HTML wrapper package like htmlKona to the mix. But even this
approach, though providing some flexibility with simple screen changes, still does not
shield you from a change in the presentation format itself. For example, if your
presentation changed from HTML to DHTML, you would still need to ensure that
wrapper packages were compliant with the new format. In a worst-case scenario, if a
wrapper package is not available, you may end up hardcoding the presentation within
the dynamic content. So, what is the solution? As you shall soon see, one approach
would be to use both JSP and servlet technologies for building application systems.

Differing philosophies

The early JSP specifications advocated two philosophical approaches for building
applications using JSP technology. These approaches, termed the JSP Model 1 and
Model 2 architectures, differ essentially in the location at which the bulk of the
request processing was performed. In the Model 1 architecture, shown in Figure 1, the
JSP page alone is responsible for processing the incoming request and replying back
to the client. There is still separation of presentation from content, because all data
access is performed using beans. Although the Model 1 architecture should be
perfectly suitable for simple applications, it may not be desirable for complex
implementations. Indiscriminate usage of this architecture usually leads to a
significant amount of scriptlets or Java code embedded within the JSP page,
especially if there is a significant amount of request processing to be performed.
While this may not seem to be much of a problem for Java developers, it is certainly
an issue if your JSP pages are created and maintained by designers -- which is usually
the norm on large projects. Ultimately, it may even lead to an unclear definition of
roles and allocation of responsibilities, causing easily avoidable project-management
headaches.
Figure 1: JSP Model 1 architecture

The Model 2 architecture, shown in Figure 2, is a hybrid approach for serving


dynamic content, since it combines the use of both servlets and JSP. It takes
advantage of the predominant strengths of both technologies, using JSP to generate
the presentation layer and servlets to perform process-intensive tasks. Here, the servlet
acts as the controller and is in charge of the request processing and the creation of any
beans or objects used by the JSP, as well as deciding, depending on the user's actions,
which JSP page to forward the request to. Note particularly that there is no processing
logic within the JSP page itself; it is simply responsible for retrieving any objects or
beans that may have been previously created by the servlet, and extracting the
dynamic content from that servlet for insertion within static templates. In my opinion,
this approach typically results in the cleanest separation of presentation from content,
leading to clear delineation of the roles and responsibilities of the developers and page
designers on your programming team. In fact, the more complex your application, the
greater the benefits of using the Model 2 architecture should be.

Figure 2: JSP Model 2 architecture


In order to clarify the concepts behind the Model 2 architecture, let's walk through a
detailed implementation of it: a sample online music store called Music Without
Borders.

Understanding Music Without Borders

The main view, or presentation, for our Music Without Borders online store is
facilitated by the JSP page EShop.jsp (shown in Listing 1). You will notice that the
page deals almost exclusively with presenting the main user interface of the
application to the client, and performs no processing whatsoever -- an optimal JSP
scenario. Also, notice that another JSP page, Cart.jsp(shown in Listing 2), is included
within EShop.jsp via the directive <jsp:include page="Cart.jsp" flush="true" />. 

Listing 1:
EShop.jsp
<%@ page session="true" %>
<html>
<head>
<title>Music Without Borders</title>
</head>
<body bgcolor="#33CCFF">
<font face="Times New Roman,Times" size="+3">
Music Without Borders
</font>
<hr><p>
<center>
<form name="shoppingForm"
action="/examples/servlet/ShoppingServlet"
method="POST">
<b>CD:</b>
<select name=CD>
<option>Yuan | The Guo Brothers | China | 4.95</option>
<option>Drums of Passion | Babatunde Olatunji | Nigeria | 6.95</option>
<option>Kaira | Tounami Diabate| Mali | 6.95</option>
<option>The Lion is Loose | Eliades Ochoa | Cuba | 3.95</option>
<option>Dance the Devil Away | Outback | Australia | 4.95</option>
<option>Record of Changes | Samulnori | Korea | 2.95</option>
<option>Djelika | Tounami Diabate | Mali | 4.95</option>
<option>Rapture | Nusrat Fateh Ali Khan | Pakistan | 2.95</option>
<option>Cesaria Evora | Cesaria Evora | Cape Verde | 6.95</option>
<option>Ibuki | Kodo | Japan | 3.95</option>
</select>
<b>Quantity: </b><input type="text" name="qty" SIZE="3" value=1>
<input type="hidden" name="action" value="ADD">
<input type="submit" name="Submit" value="Add to Cart">
</form>
</center>
<p>
<jsp:include page="Cart.jsp" flush="true" />
</body>
</html>

Listing 2:
Cart.jsp
<%@ page session="true" import="java.util.*, shopping.CD" %>
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
if (buylist != null && (buylist.size() > 0)) {
%>
<center>
<table border="0" cellpadding="0" width="100%" bgcolor="#FFFFFF">
<tr>
<td><b>ALBUM</b></td>
<td><b>ARTIST</b></td>
<td><b>COUNTRY</b></td>
<td><b>PRICE</b></td>
<td><b>QUANTITY</b></td>
<td></td>
</tr>
<%
for (int index=0; index < buylist.size();index++) {
CD anOrder = (CD) buylist.elementAt(index);
%>
<tr>
<td><b><%= anOrder.getAlbum() %></b></td>
<td><b><%= anOrder.getArtist() %></b></td>
<td><b><%= anOrder.getCountry() %></b></td>
<td><b><%= anOrder.getPrice() %></b></td>
<td><b><%= anOrder.getQuantity() %></b></td>
<td>
<form name="deleteForm"
action="/examples/servlet/ShoppingServlet"
method="POST">
<input type="submit" value="Delete">
<input type="hidden" name= "delindex" value='<%= index %>'>
<input type="hidden" name="action" value="DELETE">
</form>
</td>
</tr>
<% } %>
</table>
<p>
<form name="checkoutForm"
action="/examples/servlet/ShoppingServlet"
method="POST">
<input type="hidden" name="action" value="CHECKOUT">
<input type="submit" name="Checkout" value="Checkout">
</form>
</center>
<% } %>
Here, Cart.jsp handles the presentation of the session-based shopping cart, which
constitutes the model in our MVC architecture. Observe the scriptlet at the beginning
of Cart.jsp:
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
if (buylist != null && (buylist.size() > 0)) {
%>

Basically, the scriptlet extracts the shopping cart from the session. If the cart is empty
or not yet created, it displays nothing; thus, the first time a user accesses the
application, she is presented with the view shown in Figure 3.

Figure 3: Music Without Borders, main view

If the shopping cart is not empty, then the selected items are extracted from the cart
one at a time, as demonstrated by the following scriptlet:
<%
for (int index=0; index < buylist.size(); index++) {
CD anOrder = (CD) buylist.elementAt(index);
%>
Once the variables describing an item have been created, they are then simply inserted
into the static HTML template using JSP expressions. Figure 4 shows the application
view after the user has placed some items in the shopping cart.

Figure 4: Music Without Borders, shopping cart view

The important thing to observe here is that the processing for all actions carried out
within either Eshop.jsp or Cart.jsp is handled by
the controller servlet, ShoppingServlet.java, which is shown in Listing 3.

Listing 3:
ShoppingServlet.java
import java.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import shopping.CD;
public class ShoppingServlet extends HttpServlet {
public void init(ServletConfig conf) throws ServletException {
super.init(conf);
}
public void doPost (HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
HttpSession session = req.getSession(false);
if (session == null) {
res.sendRedirect("http://localhost:8080/error.html");
}
Vector buylist=
(Vector)session.getValue("shopping.shoppingcart");
String action = req.getParameter("action");
if (!action.equals("CHECKOUT")) {
if (action.equals("DELETE")) {
String del = req.getParameter("delindex");
int d = (new Integer(del)).intValue();
buylist.removeElementAt(d);
} else if (action.equals("ADD")) {
//any previous buys of same cd?
boolean match=false;
CD aCD = getCD(req);
if (buylist==null) {
//add first cd to the cart
buylist = new Vector(); //first order
buylist.addElement(aCD);
} else { // not first buy
for (int i=0; i< buylist.size(); i++) {
CD cd = (CD) buylist.elementAt(i);
if (cd.getAlbum().equals(aCD.getAlbum())) {
cd.setQuantity(cd.getQuantity()+aCD.getQuantity());
buylist.setElementAt(cd,i);
match = true;
} //end of if name matches
} // end of for
if (!match)
buylist.addElement(aCD);
}
}
session.putValue("shopping.shoppingcart", buylist);
String url="/jsp/shopping/EShop.jsp";
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher(url);
rd.forward(req, res);
} else if (action.equals("CHECKOUT")) {
float total =0;
for (int i=0; i< buylist.size();i++) {
CD anOrder = (CD) buylist.elementAt(i);
float price= anOrder.getPrice();
int qty = anOrder.getQuantity();
total += (price * qty);
}
total += 0.005;
String amount = new Float(total).toString();
int n = amount.indexOf('.');
amount = amount.substring(0,n+3);
req.setAttribute("amount",amount);
String url="/jsp/shopping/Checkout.jsp";
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher(url);
rd.forward(req,res);
}
}
private CD getCD(HttpServletRequest req) {
//imagine if all this was in a scriptlet...ugly, eh?
String myCd = req.getParameter("CD");
String qty = req.getParameter("qty");
StringTokenizer t = new StringTokenizer(myCd,"|");
String album= t.nextToken();
String artist = t.nextToken();
String country = t.nextToken();
String price = t.nextToken();
price = price.replace('$',' ').trim();
CD cd = new CD();
cd.setAlbum(album);
cd.setArtist(artist);
cd.setCountry(country);
cd.setPrice((new Float(price)).floatValue());
cd.setQuantity((new Integer(qty)).intValue());
return cd;
}
}

Every time the user adds an item within EShop.jsp, the request is posted to the
controller servlet. The servlet in turn determines the appropriate action, and then
processes the request parameters for the item to be added. It then instantiates a new
CD bean (shown in Listing 4) representing the selection, and goes about updating the
shopping cart object before placing it back within the session.

Listing 4:
CD.java
package shopping;
public class CD {
String album;
String artist;
String country;
float price;
int quantity;
public CD() {
album="";
artist="";
country="";
price=0;
quantity=0;
}
public void setAlbum(String title) {
album=title;
}
public String getAlbum() {
return album;
}
public void setArtist(String group) {
artist=group;
}
public String getArtist() {
return artist;
}
public void setCountry(String cty) {
country=cty;
}
public String getCountry() {
return country;
}
public void setPrice(float p) {
price=p;
}
public float getPrice() {
return price;
}
public void setQuantity(int q) {
quantity=q;
}
public int getQuantity() {
return quantity;
}
}

Notice that we have also included additional intelligence within the servlet, so that it
understands that, if a previously added CD is reselected, it should simply increase the
count for that CD bean within the shopping cart. The controller servlet also processes
actions triggered from within Cart.jsp, such as the user deleting items from the
shopping cart, or proceeding to the checkout counter. Observe that the controller
always has complete control over which resources should be invoked in response to
specific actions. For example, changes made to the state of the shopping cart, such as
additions or deletions, cause the controller servlet to forward the request after
processing to the Eshop.jsp page. This in turn causes the page to redisplay the main
view, along with the updated contents of the shopping cart. If the user decides to
check out, the request is forwarded after processing to the Checkout.jsp page (shown
in Listing 5) by means of the following request dispatcher, as shown below:
String url="/jsp/shopping/Checkout.jsp";
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher(url);
rd.forward(req,res);

Listing 5:
Checkout.jsp
<%@ page session="true" import="java.util.*, shopping.CD" %>
<html>
<head>
<title>Music Without Borders Checkout</title>
</head>
<body bgcolor="#33CCFF">
<font face="Times New Roman,Times" size=+3>
Music Without Borders Checkout
</font>
<hr><p>
<center>
<table border="0" cellpadding="0" width="100%" bgcolor="#FFFFFF">
<tr>
<td><b>ALBUM</b></td>
<td><b>ARTIST</b></td>
<td><b>COUNTRY</b></td>
<td><b>PRICE</b></td>
<td><b>QUANTITY</b></td>
<td></td>
</tr>
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
String amount = (String) request.getAttribute("amount");
for (int i=0; i < buylist.size();i++) {
CD anOrder = (CD) buylist.elementAt(i);
%>
<tr>
<td><b><%= anOrder.getAlbum() %></b></td>
<td><b><%= anOrder.getArtist() %></b></td>
<td><b><%= anOrder.getCountry() %></b></td>
<td><b><%= anOrder.getPrice() %></b></td>
<td><b><%= anOrder.getQuantity() %></b></td>
</tr>
<%
}
session.invalidate();
%>
<tr>
<td> </td>
<td> </td>
<td><b>TOTAL</b></td>
<td><b>$<%= amount %></b></td>
<td> </td>
</tr>
</table>
<p>
<a href="/examples/jsp/shopping/EShop.jsp">Shop some more!</a>
</center>
</body>
</html>

Checkout.jsp simply extracts the shopping cart from the session and the total amount
for the request, and then displays the selected items and their total cost. Figure 5
shows the client view upon checkout. Once the user goes to the checkout counter, it is
equally important to get rid of the session object. That is taken care of by having
a session.invalidate() invocation at the end of the page. This process is necessary
for two reasons. First, if the session is not invalidated, the user's shopping cart is not
reinitialized; if the user then attempts to commence another round of shopping upon
checkout, her shopping cart will continue to hold items that she has already
purchased. The second reason is that if the user simply left the site upon checkout, the
session object will not be garbage collected and will continue to take up valuable
system resources until its lease period expires. Since the default session-lease period
is about 30 minutes, this can quickly lead to the system running out of memory in a
high-volume system. Of course, we all know what happens to an application that runs
out of system resources!

Figure 5: Music Without Borders, checkout view

Notice that all the resources for this application are session aware, since the model
here is stored within the session. Consequently, you must ensure that the user does not
somehow access the controller directly, even by mistake. You can take care of this by
implementing the automatic client redirection to the error page (shown in Listing 6)
when the controller detects the absence of a valid session.

About the author

Govind Seshadri is an Enterprise Java Guru for jGuru.com, and the author of
Enterprise Java Computing -- Applications and Architecture from Cambridge
University Press (1999). Learn more about Govind at jGuru.com. JavaWorld and
jGuru have formed a partnership to help the community better understand server-side
Java technology. Together, JavaWorld and jGuru are jointly producing articles, free
educational Web events, and working together on the JavaWorld bookstore and Web-
based training.

Read more about Core Java in JavaWorld's Core Java section.

Listing 6:
error.html
<html>
<body>
<h1>
Sorry, there was an unrecoverable error! <br>
Please try <a href="/examples/jsp/shopping/EShop.jsp">again</a>.
</h1>
</body>
</html>

Deploying Music Without Borders

I will assume that you are using the latest version of JavaServer Web Development
Kit (JSWDK) from Sun for running the example. If not, see the Resources section to
find out where to get it. Assuming that the server is installed in \jswdk-1.0.1, its
default location in Microsoft Windows, deploy the Music Without Borders application
files as follows:

 Create shopping directory under \jswdk-1.0.1\examples\jsp


 Copy EShop.jsp to \jswdk-1.0.1\examples\jsp\shopping
 Copy Cart.jsp to \jswdk-1.0.1\examples\jsp\shopping
 Copy Checkout.jsp to \jswdk-1.0.1\examples\jsp\shopping
 Compile the .java files by typing javac *.java
 Copy ShoppingServlet.class to \jswdk-1.0.1\webpages\Web-Inf\servlets
 Create shopping directory under \jswdk-1.0.1\examples\Web-Inf\jsp\beans
 Copy CD.class to \jswdk-1.0.1\examples\Web-Inf\jsp\beans\shopping
 Copy error.html to \jswdk-1.0.1\webpages
 Once your server has been started, you should be able to access the application
usinghttp://localhost:8080/examples/jsp/shopping/EShop.jsp as the URL
Leveraging JSP and servlets

In this example, we have examined in detail the level of control and flexibility
provided by the Model 2 architecture. In particular, we've seen how the best features
of servlets and JSP pages can be exploited to maximize the separation of presentation
from content. Properly applied, the Model 2 architecture should result in the
concentration of all of the processing logic in the hands of the controller servlet, with
the JSP pages responsible only for the view or presentation. However, the downside
of using the Model 2 approach is its complexity. Consequently, it may be desirable to
use the Model 1 approach for simpler applications.

All contents copyright 1995-2011 Java World, Inc. http://www.javaworld.com

Você também pode gostar