java服务端:理解jsp model 2架构体系
---运用mvc设计模式进行开发
原文发表在http://www.javaworld.com/javaworld/jw-12-1999/jw-12-ssj-jspmvc.html
By Govind Seshadri, JavaWorld.com, 12/29/99
java开发者喜欢jsp有很多原因,很多人是因为喜欢其一次编写,随处运行提供web交互,有的人是喜欢其是一种很容易上手学习的服务端脚本技术,但是,jsp最大的优势是他有效的把表现从内容中间分离出来了,从这边文章,我们深入的来分析一下,利用model2 架构把显示层和内容层进行分离,model2也可以看做是设计模式mvc的一种实现形式,请注意你应该在此之前熟悉jsp和servlet的基本编程,在文章中我将不会讲解语法;
serlvets怎么啦?
当jsp很好的提供了显示和内容的分离,很多人都在疑惑servlets是否应该废除,servltes的功效是毫无疑问的,他是服务端优秀的处理过程,就架构而言,你可以把jsp看做是高层次的servltes的实现,但是,你不能不加区别的使用servlets,他们不是对每个人都合适,比如:当页面设计师利用html或者xml工具能够很轻松的写出一个jsp页面的时候,servltes更适合后台开发人员,因为他们经常使用ide来写程序,当部署servltes的时候,开发人员设置必须十分小心确保页面和内容之间没有过分耦合,你可以加入第三方封装包比如mix或者htmlkona来实现这一点,但是通过这种途径,能够对一些简单的页面的改变提供良好的可扩展性,但是你依然不能免除一些页面重大排版所带来的改变,一个简单的例子就是,假如你的html页面必须变更成为dhtml,你也不得不把你的封装包屈从一个新的格式,最坏的情况下,如果一个封装包你无法得到,你必须在动态显示的地方硬编码,那么,解决的办法是什么?你将很快就可以看到,那就是利用servlets和jsp一起来构建应用;
不同的观点:
在早些时候的的jsp说明书里面提倡两种jsp的使用方式,分别是jsp model1
和jsp model2,两者的本质上的区别在于请求的处理,根据图我们可以看出来,model1中,jsp不仅要负责处理客服端的请求,并且还要负责服务端到客服端的页面的返回,但是这里我们依旧把显示和内容进行了分离,因为数据库的数据的访问我们都是通过的javabean来实现的,虽然model1对小的应用来时一种完美的架构,但是对于复杂的应用,他是不可以取的,不加区别的使用这种方式常常导致我们在页面上编入很多java代码,尤其是这里有很多请求需要执行和处理的时候,这也许对程序设计人员来时不是什么问题,但是对于制作和维护页面的设计师和美工人员来说确是噩梦,很多时候导致了开发人员之间的职责难以定义,也让工程管理的人员头疼;
图二显示了model的架构图
对于提供动态内容采用的是混合模式,jsp和model混合使用,他体现了两者各自的长处和优势,利用jsp生成的页面来提供显示,利用servlets来处理客服端的请求,在这里,servlets扮演了一个控制器的角色,他负责生成jsp页面所要用到的一个javabean和对象,并且控制了流程的处理,并且根据不同的请求来决定用转发到哪一个jsp页面,请特别注意,这里的jsp不存在业务逻辑的处理,他的职责很简单,就是取得先前的serlets所生成的javabean和对象,和一些servlets插入到静态模板中的动态内容,使得你的团队中的页面设计人员和程序设计师之间的职责格外的清晰明了,事实上,你的系统越复杂,那么你采用model2所带来的利益就越大;
为了弄明白model2体系架构的一些具体的概念,我们来实现一个在线音乐商店,名字叫做music without border;
理解 music without border:
一个主试图,也就是显示层,通过一个jsp页面Eshop.jsp来展示,如下:你注意到页面上主要是提供了一些用户接口,并没有多少的业务逻辑的处理,在Eshop.jsp页面中,采用了指令<jsp:include page="Cart.jsp" flush="true" />.加入了vcart.jsp页面;
Echop.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>
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>
<% } %>
在这里,cart.jsp负责显示了一个基于session的购物车的显示,注意其开头的脚本:
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
if (buylist != null && (buylist.size() > 0)) {
%>
基本上,这段代码把一个购物车的模型对象从会话中取出,如果购物车是空的或者没有创建,那么他就会什么东西都不显示,所以,在用户第一次访问本系统的时候,他的界面如下:
如果,购物车不是空的,那么所选择的商品条目就会一条一条的从购物车中取出,并且通过一下脚本娴熟出来:
<%
for (int index=0; index < buylist.size(); index++) {
CD anOrder = (CD) buylist.elementAt(index);
%>
一旦一个描述商品条目的变量被创建,那么他们会被简单的用vjsp表达式插入到一个html的静态模板中
图四显示了用户将一些商品放入了购物车以后的页面:
这里的重点是观察Echop.jsp
所产生的动作的处理或处理cart.jsp的控制器ShoppingServlet.java,;代码如下:
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;
}
}
每次页面中加入一个商品的时候,请求就会被发哦那个到这个控制器的servlets,这个servlets轮流的对每个动作作出决定,他们处理了被加入的商品的请求阐述,他实例化了一个代表所选择vcd的cdbean,并且更新了购物车之中的记录;
CDBean:
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;
}
}
可见,我们在servlets包括一了一些额外的智能化的处理,那就是如果一种商品在购物车里面已经有了,当他再次被选择的时候,我们只是增加了其的数量,其中控制器也处理cart.jsp所触发的动作,比如用户删除了购物车中的商品和处理柜台结账等等,控制器还负责页面之间的一些跳转,比如用户选择结账,那么页面就会跳转到结账页面去:代码如下:
String url="/jsp/shopping/Checkout.jsp";
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher(url);
rd.forward(req,res);
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>
结账的页面列出了session中的购物车,并且计算出价钱的总和;在页面的尾部,我们调用了session.invalidate(),这点很重要,因为如果不调用,那么,在结账之后,用户的购物车不会再次初始化,当用户再次购物的时候,那么他在选择结账的时候不得不为他已经买单了的物品踩在消费,第二点就是,如果用户结账离开了,那么session里面的对象不会被垃圾回收,会继续占用服务端的系统资源知道session有效期剩下的时间失效,session的默认失效时间是30分钟,那么系统将在剩下的时间内内存将搞负荷的工作,当然,我们都知道一个应用在超出系统资源环境下工作的后果;