那么,servlet有什么問題?
既然jsp用來(lái)提供動(dòng)態(tài)web內(nèi)容并且對(duì)于從表現(xiàn)層中分離內(nèi)容很不錯(cuò),一些人也許想知道為什么servlet要從jsp中脫離出來(lái)與它并列。servlet的功用沒有問題。它對(duì)于服務(wù)端處理干得很好,而且,由于它重要的已安裝基礎(chǔ),就適合這個(gè)。實(shí)際上,從結(jié)構(gòu)上說(shuō),你可以把jsp看作實(shí)現(xiàn)為servlet 2.1 api的擴(kuò)展的servlet高級(jí)抽象。仍然不應(yīng)該不加區(qū)別地使用servlet;它可能不會(huì)適用于每一個(gè)人。舉個(gè)例子來(lái)說(shuō),盡管頁(yè)面設(shè)計(jì)者能夠很容易地使用常規(guī)html或者xml工具編寫jsp頁(yè)面,而servlet通常更適合后臺(tái)開發(fā)者,他們通常使用某種ide——一個(gè)通常需要高層次的編程專門知識(shí)的過(guò)程。當(dāng)發(fā)布servlet時(shí),即使開發(fā)者也必須留意和確認(rèn)在內(nèi)容和表現(xiàn)之間沒有緊耦合。通常,你可以通過(guò)加入第三方的html封裝包比如htmlkona來(lái)做這個(gè)。即使這樣做了,盡管帶來(lái)了一些簡(jiǎn)單的對(duì)于屏幕變化的伸縮性,仍然不能為你防止免受表現(xiàn)格式自身的變化的影響。例如,如果你的表現(xiàn)形式從html轉(zhuǎn)變到dhtml,你將仍然需要確認(rèn)你的封裝包是否兼容這種新格式。在最壞的情況下,如果封裝包不能用了,你可能最終會(huì)在動(dòng)態(tài)內(nèi)容內(nèi)部硬編碼表現(xiàn)形式。那么,解決辦法是什么?就像你你將要看到的,一個(gè)辦法將會(huì)同時(shí)使用jsp和servlet來(lái)創(chuàng)建應(yīng)用系統(tǒng)。
差異哲學(xué)
早期的jsp規(guī)范主張兩種使用jsp技術(shù)創(chuàng)建應(yīng)用的哲學(xué)思路。這兩種思路,用術(shù)語(yǔ)來(lái)說(shuō)就是jsp模式1和模式2,本質(zhì)上的區(qū)別在于大部分請(qǐng)求的處理發(fā)生的位置。在模式1架構(gòu)中,如圖1所示,jsp頁(yè)面獨(dú)立地負(fù)責(zé)處理請(qǐng)求和發(fā)送反饋給客戶端。這里仍然有內(nèi)容和表現(xiàn)的分離,因?yàn)樗械臄?shù)據(jù)訪問是使用bean完成的。盡管模式1架構(gòu)應(yīng)該很適合簡(jiǎn)單應(yīng)用,但是對(duì)于復(fù)雜的實(shí)現(xiàn)是不可取的。這種結(jié)構(gòu)的任意使用通常會(huì)導(dǎo)致大量的腳本和java代碼嵌入到j(luò)sp頁(yè)面中,特別是在有大量的請(qǐng)求需要處理的情況下。盡管這可能對(duì)java開發(fā)者來(lái)說(shuō)不是一個(gè)大問題,但是卻無(wú)疑是一個(gè)問題,如果你的jsp頁(yè)面是由設(shè)計(jì)師創(chuàng)建和維護(hù)的話——在大項(xiàng)目中通常如此。最終,這個(gè)問題甚至?xí)?dǎo)致角色定義和責(zé)任分配的混亂,引起本可以輕松避免的項(xiàng)目管理的麻煩。

圖1:jsp模式1結(jié)構(gòu)
模式2架構(gòu)如圖2所示,是一個(gè)為動(dòng)態(tài)內(nèi)容服務(wù)的混合方案,因?yàn)樗瑫r(shí)使用了servlet和jsp。它利用了兩種技術(shù)的優(yōu)勢(shì),使用jsp產(chǎn)生表現(xiàn)層而servlet負(fù)責(zé)執(zhí)行敏感任務(wù)。在這里,servlet扮演控制器的角色,負(fù)責(zé)請(qǐng)求處理和產(chǎn)生jsp要使用的bean和對(duì)象,以及根據(jù)客戶的動(dòng)作決定下一步轉(zhuǎn)發(fā)到哪一個(gè)jsp頁(yè)面。特別要注意的是jsp頁(yè)面內(nèi)部并沒有處理邏輯;它只是簡(jiǎn)單地負(fù)責(zé)取得可能是servelet事先創(chuàng)建的對(duì)象和bean,并為在了靜態(tài)模版中插入從servlet釋放出動(dòng)態(tài)內(nèi)容。我的觀點(diǎn)是,這個(gè)辦法一般會(huì)形成最干凈徹底的表現(xiàn)與內(nèi)容的分離,使得你的開發(fā)團(tuán)隊(duì)里的開發(fā)者和頁(yè)面設(shè)計(jì)師的角色與責(zé)任能夠清晰。實(shí)際上,你的應(yīng)用越復(fù)雜,使用模式2帶來(lái)的好處就越多。

圖2:jsp模式2結(jié)構(gòu)
為了弄清模式2背后的概念,我們來(lái)參觀一個(gè)細(xì)化的具體實(shí)現(xiàn):一個(gè)叫做音樂無(wú)界的在線音樂商店樣品。
了解音樂無(wú)界
主視圖,或者說(shuō)表現(xiàn)層,對(duì)于我們的音樂無(wú)界由jsp頁(yè)面eshop.jsp產(chǎn)生(見清單1)。你會(huì)注意到這個(gè)頁(yè)面幾乎僅僅處理這個(gè)應(yīng)用的主要用戶界面,而且沒有做任何處理工作——一個(gè)最佳的jsp腳本。也注意一下另一個(gè)jsp頁(yè)面,cart.jsp(見清單2),通過(guò)指令<jsp:include page="cart.jsp" flush="true" />包含在eshop.jsp之內(nèi)。
清單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> |
清單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> <% } %> |
這里,cart.jsp處理基于session的購(gòu)物車的表現(xiàn)形式,它指定了我們的mvc結(jié)構(gòu)中的模型。觀察cart.jsp開頭這一段腳本:
<%
vector buylist = (vector) session.getvalue("shopping.shoppingcart");
if (buylist != null && (buylist.size() > 0)) {
%>
基本上,這段腳本從session中提出了購(gòu)物車。如果購(gòu)物車為空或者還未創(chuàng)建,它不會(huì)顯示任何東西;因此,當(dāng)用戶第一次訪問的時(shí)候,他見到的頁(yè)面如圖3。

圖3:音樂無(wú)界,主視圖
如果購(gòu)物車不是空的,那么已選中的物品會(huì)一次一個(gè)地從購(gòu)物車中被提出,像下面的腳本示范的那樣:
<%
for (int index=0; index < buylist.size(); index++) {
cd anorder = (cd) buylist.elementat(index);
%>
一旦描述物品的變量已創(chuàng)建,它們就簡(jiǎn)單地被jsp表達(dá)式插入到靜態(tài)html模版中去。圖4顯示了用戶已經(jīng)放了一些東西到購(gòu)物車?yán)锶ナ堑那闆r。

圖4:音樂無(wú)界,購(gòu)物車視圖
這里要注意的重要的一件事是對(duì)所有動(dòng)作的處理既不發(fā)生在eshop.jsp也不在cart.jsp里,而是由控制器servlet,shoppingservlet.java處理,見清單3:
清單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; } } |
清單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; } } |
注意我們?cè)谶@個(gè)servlet中還包括了額外的智能,因此它能夠知道如果選擇了一張已在購(gòu)物車中的cd,那么應(yīng)該簡(jiǎn)單地增加session中cd bean的計(jì)數(shù)。它也處理從cart.jsp中觸發(fā)的動(dòng)作,比如用戶從購(gòu)物車中刪除物品,或是繼續(xù)去收銀臺(tái)結(jié)帳。注意控制器總是對(duì)哪個(gè)資源應(yīng)該被調(diào)用來(lái)對(duì)特定的動(dòng)作產(chǎn)生回饋有完全的控制權(quán)。例如,對(duì)購(gòu)物車狀態(tài)的改變,像增加和刪除,會(huì)引起控制器將請(qǐng)求處理后轉(zhuǎn)發(fā)給eshop.jsp頁(yè)面。這樣引起該頁(yè)面依照已更新的購(gòu)物車依次重新顯示主視圖。如果用戶決定結(jié)帳,則請(qǐng)求被處理后轉(zhuǎn)發(fā)給checkout.jsp(見清單5),通過(guò)后面的請(qǐng)求分配器,象下面顯示的這樣:
string url="/jsp/shopping/checkout.jsp";
servletcontext sc = getservletcontext();
requestdispatcher rd = sc.getrequestdispatcher(url);
rd.forward(req,res);
清單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僅僅從session中提出購(gòu)物車并為此請(qǐng)求提取出總金額,然后顯示選中的物品和他們的總價(jià)格。圖5顯示了結(jié)算時(shí)的用戶視圖。一旦用戶去結(jié)帳,刪除session對(duì)象是同樣重要的。這個(gè)由頁(yè)面末端的session.invalidate()調(diào)用來(lái)完成。有兩個(gè)理由必須這樣做。第一,如果沒有使session無(wú)效,用戶的購(gòu)物車不會(huì)重新初始化;如果用戶結(jié)帳后試圖開始新一輪的采購(gòu),她的購(gòu)物車會(huì)繼續(xù)保存著已經(jīng)付過(guò)錢的物品。第二,如果用戶結(jié)帳后僅僅是離開了網(wǎng)站,這個(gè)session對(duì)象不會(huì)被垃圾收集機(jī)制回收而是繼續(xù)占用寶貴的系統(tǒng)資源直到租約到期。因?yàn)槿笔〉膕ession租約時(shí)間是大約三十分鐘,在一個(gè)大容量系統(tǒng)上這將會(huì)很快導(dǎo)致系統(tǒng)內(nèi)存耗盡。當(dāng)然,我們都知道對(duì)一個(gè)耗盡了系統(tǒng)內(nèi)存的應(yīng)用程序會(huì)發(fā)生什么。

圖5:結(jié)賬視圖
注意這個(gè)應(yīng)用所有的資源都是session相關(guān)的,因?yàn)檫@里的模式存儲(chǔ)在session里。因此,你必須確保用戶不會(huì)因?yàn)槟承┰蛏踔劣捎阱e(cuò)誤直接訪問控制器。你可以在控制器檢測(cè)到缺少有效session的時(shí)候讓客戶端自動(dòng)轉(zhuǎn)向到一個(gè)錯(cuò)誤頁(yè)面(見列表6),來(lái)避免這種情況的發(fā)生。
列表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> |
部署音樂無(wú)界
我假定你正在使用來(lái)自sun的最新版本的javaserver web development kit (jswdk)來(lái)運(yùn)行這個(gè)例子。如果不是,參看資源小節(jié)去看看到哪里取得它。假設(shè)服務(wù)器安裝在/jswdk-1.0.1,這是microsoft windows系統(tǒng)下的缺省路徑,可以象下面這樣部署音樂無(wú)界應(yīng)用:
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 using http://localhost:8080/examples/jsp/shopping/eshop.jsp as the url
只要你的服務(wù)器啟動(dòng),你應(yīng)該可以使用url http://localhost:8080/examples/jsp/shopping/eshop.jsp來(lái)訪問這個(gè)應(yīng)用程序。
利用jsp和servlet
著這個(gè)例子里,我們從細(xì)節(jié)上檢查了控制的層次和模式2架構(gòu)提供的靈活性。實(shí)際上,我們已經(jīng)看到了servlet和jsp頁(yè)面最好的特性是如何開發(fā)出最大化的內(nèi)容與表現(xiàn)的剝離。只要正確地應(yīng)用,模式2架構(gòu)會(huì)將所有的處理邏輯集中到控制器servlet手里,而jsp頁(yè)面只負(fù)責(zé)視圖或者說(shuō)表現(xiàn)層的工作。然而,使用模式2結(jié)構(gòu)的阻力在于它的復(fù)雜性。因此,對(duì)于簡(jiǎn)單應(yīng)用使用模式1也是可以接受的。
新聞熱點(diǎn)
疑難解答
圖片精選