Understanding JavaServer Pages Model 2 architecture

advertisement
Understanding JavaServer Pages
Model 2 architecture
Exploring the MVC design pattern
Summary
By developing a familiar Web-based shopping cart, you'll learn how to utilize
the Model-View-Controller (MVC) design pattern and truly separate
presentation from content when using JavaServer Pages. Govind Seshadri
shows you out how easy it can be. (2,000 words)
By Govind Seshadri
Printer-friendly version |
Mail this to a friend
Advertisement
espite 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
So, what's wrong with servlets?
series!
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
 Welcome to
aside for JSP. The utility of servlets is not in question.
the serverThey are excellent for server-side processing, and,
side Java
with their significant installed base, are here to stay.
series
In fact, architecturally speaking, you can view JSP as a
 Create
forward-compatible beans
high-level abstraction of servlets that is implemented
in EJB, Part 1
as an extension of the Servlet 2.1 API. Still, you
 Understanding
shouldn't use servlets indiscriminately; they may not
JavaServer Pages Model 2
be appropriate for everyone. For instance, while page
Architecture
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 | $14.95</option>
<option>Drums of Passion | Babatunde Olatunji | Nigeria | $16.95</option>
<option>Kaira | Tounami Diabate| Mali | $16.95</option>
<option>The Lion is Loose | Eliades Ochoa | Cuba | $13.95</option>
<option>Dance the Devil Away | Outback | Australia | $14.95</option>
<option>Record of Changes | Samulnori | Korea | $12.95</option>
<option>Djelika | Tounami Diabate | Mali | $14.95</option>
<option>Rapture | Nusrat Fateh Ali Khan | Pakistan | $12.95</option>
<option>Cesaria Evora | Cesaria Evora | Cape Verde | $16.95</option>
<option>Ibuki | Kodo | Japan | $13.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>
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!
Checkout.jsp
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.
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\WebInf\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 using
http://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.
Printer-friendly version |
Mail this to a friend
Resources



Download the latest JavaServer Web Development Kit:
http://java.sun.com/products/jsp/download.html
Get the source code for the example:
http://www.javaworld.com/jw-12-1999/ssj/ShoppingCart.jar
Discuss this article in a JavaWorld-jGuru forum scheduled for
December 9, 1999. To register, go to:
http://www.jguru.com/jguru/jwevent/jwRegister.jsp
Download