迷你MVC教程——命令行方式創(chuàng)建第一個(gè)Servelt小程序
本文節(jié)選自《Head First Servlets and JSP》第3章 MVC實(shí)戰(zhàn),根據(jù)個(gè)人配置和實(shí)操情況有所刪改母廷。本人注解部分用斜體表示。圖片截取自原書隅熙,部分為個(gè)人操作截圖沐旨。本人電腦配置win10x64, tomcat7, jdk10。本教程的特色處在于全程采用手工編寫和命令行結(jié)合噪伊,不借助IDE開發(fā)環(huán)境簿煌,適合初次接觸Servlet者當(dāng)作練習(xí)的小項(xiàng)目。任何問(wèn)題請(qǐng)?jiān)谙路搅粞浴?/p>
構(gòu)建小型Web應(yīng)用步驟
- 分析用戶的視圖以及高層體系結(jié)構(gòu)
- 創(chuàng)建用于開發(fā)這個(gè)項(xiàng)目的開發(fā)環(huán)境
- 創(chuàng)建用于部署這個(gè)項(xiàng)目的部署環(huán)境
- 對(duì)Web應(yīng)用的各個(gè)組件完成迭代式的開發(fā)和測(cè)試
(基于Beer Advisor的案例鉴吹。根據(jù)用戶選擇的酒色提供酒的品牌建議的servlet小程序)
用戶視圖
- 用戶最初請(qǐng)求的HTML表單姨伟,生成HTTP POST請(qǐng)求
- 處理請(qǐng)求返回的result.jsp
體系結(jié)構(gòu)
- 客戶請(qǐng)求得到form.html頁(yè)面
- 容器獲得form.html頁(yè)面
- 容器把這個(gè)頁(yè)面返回給瀏覽器,用戶再在瀏覽器上回答表單上的問(wèn)題
- 瀏覽器把請(qǐng)求數(shù)據(jù)發(fā)送給容器
- 容器根據(jù)URL查找正確的servlet豆励,并把請(qǐng)求傳遞給這個(gè)servlet
- servlet調(diào)用BeerExpert尋求幫助
- BeerExpert類返回一個(gè)回答夺荒,servlet把這個(gè)回答增加到請(qǐng)求對(duì)象
- servlet把請(qǐng)求轉(zhuǎn)發(fā)給JSP
- JSP從請(qǐng)求對(duì)象得到回答
- JSP為容器生成一個(gè)頁(yè)面
- 容器將jsp頁(yè)面返回給用戶
開發(fā)環(huán)境
IDE項(xiàng)目的目錄結(jié)構(gòu)
部署環(huán)境
將Web項(xiàng)目部署到容器中
迭代式的開發(fā)和測(cè)試
- 構(gòu)建和測(cè)試用戶最初請(qǐng)求的HTML表單
- 構(gòu)建控制器servlet的第一個(gè)版本,并用HTML表單測(cè)試這個(gè)控制器良蒸。這個(gè)版本通過(guò)HTML表單來(lái)調(diào)用技扼,并打印出它接收到的參數(shù)
- 為模型類構(gòu)建一個(gè)測(cè)試類,然后構(gòu)建并測(cè)試模型類本身
- 把servlet升級(jí)到第2版诚啃。這個(gè)版本增加了一個(gè)功能淮摔,可以調(diào)用模型類來(lái)得到啤酒建議。
- 構(gòu)建JSP, 把servlet升級(jí)到第3版本(增加一個(gè)功能始赎,可以把表示流分派到JSP完成)和橙,然后再測(cè)試整個(gè)應(yīng)用。
第一個(gè)表單頁(yè)面的HTML
form.html包含標(biāo)題文本造垛,一個(gè)下拉列表魔招,還有一個(gè)提交按鈕(原書代碼)
<html>
<body>
<h1 align="center">
Beer Selection Page
</h1>
<!--為什么選擇POST而不是GET? HTML認(rèn)為這就是要調(diào)用servlet。在你的目錄結(jié)構(gòu)里沒(méi)有一個(gè)叫“SelectBeer.do”的東西五辽。這只是一個(gè)邏輯名-->
<form method="POST" action="SelectBeer.do">
<p>
Select beer characteristics
</p>
Color:
<select name="color" size="1">
<!--我們就是這樣拆個(gè)那就下拉菜單的办斑,你可以有自己不同的選項(xiàng)-->
<option value="light">light</option>
<option value="amber">amber</option>
<option value="brown">brown</option>
<option value="dark">dark</option>
</select>
<br><br>
<center>
<input type="SUBMIT">
</center>
</form>
</body>
</html>
部署和測(cè)試開始頁(yè)面
- 在開發(fā)環(huán)境中創(chuàng)建HTML
創(chuàng)建這個(gè)HTML文件,取名為form.html,然后保存在開發(fā)環(huán)境的/beerV1/web/目錄下
- 把這個(gè)文件復(fù)制到部署環(huán)境
把form.html文件的一個(gè)副本放在tomcat/webapps/Beer-vl/中
- 在開發(fā)環(huán)境中創(chuàng)建DD
創(chuàng)建XML文檔乡翅,取名為web.xml, 把它保存在開發(fā)環(huán)境的/beer/etc/目錄下(原書代碼)
<!--沒(méi)有必要知道這是什么意思鳞疲,只需要照著輸入就行-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<servlet>
<!--這是一個(gè)虛構(gòu)的名字,只能在DD的其他部分使用-->
<servlet-name>Ch3 Beer</servlet-name>
<!--servlet類文件的完全限定名-->
<servlet-class>com.example.web.BeerSelect</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Ch3 Beer</servlet-name>
<!--不要忘記最前面有一個(gè)斜線蠕蚜。我們希望用戶這樣引用“servlet.do”只是一個(gè)約定-->
<url-pattern>/SelectBeer.do</url-pattern>
</servlet-mapping>
</web-app>
由于原書代碼是tomcat5, 版本有所變化尚洽,可以到tomcat/webapps/ROOT/WEB-INF自帶目錄下拷貝了一份web-xml文件,并添加相應(yīng)的映射語(yǔ)句靶累。
- 把這個(gè)文件復(fù)制到部署環(huán)境
把web.xml文件的一個(gè)副本放在tomcat/webapps/Beer-v1/WEB-INF/
- 啟動(dòng)Tomcat
Tomcat既作為Web服務(wù)器腺毫,又作為Web容器。要啟動(dòng)Tomcat, 先用cd命令切換到tomcat主目錄挣柬,再運(yùn)行bin/startup.sh
命令行中用cd切換到tomcat/bin目錄下(tomcat根據(jù)版本名稱不同)潮酒,win系統(tǒng)運(yùn)行startup.bat
- 測(cè)試頁(yè)面
在瀏覽器中打開這個(gè)HTML頁(yè)面,為此鍵入:
邏輯名映射到servlet類文件
以下部分為xml文件中配置的詳解
- 用戶填寫表單邪蛔,然后點(diǎn)擊submit急黎。瀏覽器生成了以下請(qǐng)求URL:
在用戶發(fā)送的http post請(qǐng)求中,“/Beer-v1”不是路徑的一部分侧到。在form.html中叁熔,它只說(shuō):<form method="POST" action="SelectBeer.do>"
但瀏覽器為請(qǐng)求追加了“/Beer-v1/”,因?yàn)橛脩粽?qǐng)求就來(lái)自這里床牧。換句話說(shuō),form.html中的“SelectBeer.do”相對(duì)于其所在頁(yè)面的URL遭贸。在這里戈咳,就是相對(duì)于Web應(yīng)用的根:“/Beer-v1”
- 容器搜索DD, 找到
<url-pattern>
與/SelectBeer.do匹配的一個(gè)<servlet-mapping>
, 這里的斜線{/}表示W(wǎng)eb應(yīng)用的上下文根,SelectBeer.do就是資源的邏輯名 - 容器看到對(duì)應(yīng)這個(gè)
<url-pattern>
的<servlet-name>
是“Ch3 Beer”壕吹。但是這并不是實(shí)際servlet類文件的名字著蛙。“Ch3 Beer”是servlet名耳贬,而不是servlet類的名字踏堡。
對(duì)容器來(lái)說(shuō),servlet只是在DD中<servlet>
標(biāo)記下的一個(gè)東西咒劲。servlet名只在DD中使用顷蟆,以便DD的其他部分建立與該servlet的映射
- 容器查找
<servlet-name>
為“Ch3 Beer”的<servlet>
標(biāo)記 - 根據(jù)
<servlet>
標(biāo)記中的<servlet-class>
,容器可以知道有哪個(gè)servlet類負(fù)責(zé)處理這個(gè)請(qǐng)求。如果這個(gè)servlet還沒(méi)有初始化腐魂,就會(huì)加載類帐偎,并初始化servlet - 容器開始一個(gè)新線程來(lái)處理這個(gè)請(qǐng)求,并把請(qǐng)求傳遞給這個(gè)線程(傳遞給servelt的service()方法)
- 容器把響應(yīng)(通過(guò)Web服務(wù)器)發(fā)回給用戶
控制器servlet的第1版
確保HTML頁(yè)面能適當(dāng)?shù)卣{(diào)用servlet, 而且servlet能正確地接收HTML參數(shù)蛔屹。
(原書代碼)
//確保與前面創(chuàng)建的開發(fā)結(jié)構(gòu)和部署結(jié)構(gòu)匹配
package com.example.web;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
//HttpServlet擴(kuò)展了GenericServlet,GenericServlet則實(shí)現(xiàn)了Servlet接口
public class BeerSelect extends HttpServlet{
//我們使用doPOST來(lái)處理HTTP請(qǐng)求削樊,因?yàn)镠TML表單指出,method=POST
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
//這個(gè)方法來(lái)自ServletResponse接口
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("Beer Selection Advice<br>");
//這個(gè)方法來(lái)自ServletRequest接口。注意這個(gè)參數(shù)與HTML<select>標(biāo)記中"name"屬性的值匹配
String c = request.getParameter("color");
//這里我們沒(méi)有返回建議漫贞,只是把測(cè)試信息顯示出來(lái)
out.println("<br>Got beer color "+c);
}
}
編譯甸箱、部署和測(cè)試控制器servlet
編譯servlet
用-d標(biāo)志編譯servlet,把類放在開發(fā)環(huán)境中
原書
win系統(tǒng)
注解:-classpath用于設(shè)置臨時(shí)環(huán)境變量,指定查找用戶類文件和注釋處理程序的位置迅脐;本例中由于編譯servlet需要用到額外類庫(kù)芍殖,tomcat提供了這些jar包,故選定tomcat/lib/servlet-api.jar為環(huán)境變量仪际。書中還添加了classes和"."(代表當(dāng)前目錄)作為路徑围小,其實(shí)可以略去。
-d用于指定編譯生成的class文件存放目錄树碱,本例中存放于classes路徑中肯适,由于servlet的package語(yǔ)句,會(huì)自動(dòng)生成com.example.web目錄(若不存在)成榜。
最后框舔,添上servlet類的存放路徑,若存放在當(dāng)前目錄則直接輸入文件名赎婚。
部署servlet
要部署servlet, 建立.class文件的一個(gè)副本刘绣,并把它移到部署結(jié)構(gòu)的/Beer-v1/WEB-INF/classes/com/example/web/目錄下
測(cè)試
- 重啟tomcat
直接在tomcat/bin目錄下輸入startup.bat命令即可重啟,無(wú)須關(guān)閉
- 啟動(dòng)瀏覽器挣输,訪問(wèn):
- 選擇一種啤酒顏色纬凤,點(diǎn)擊“Submit”
- 如果你的servlet能正常運(yùn)行,就能在瀏覽器上看到servlet的響應(yīng)顯示為:
Beer Selection Advice
Got beer color brown
構(gòu)建和測(cè)試模型類
模型規(guī)范
- 包應(yīng)當(dāng)是com.example.model
- 其目錄結(jié)構(gòu)應(yīng)當(dāng)是/WEB-INF/classes/com/model
- 提供一個(gè)方法getBrands(), 取一個(gè)喜歡的啤酒顏色(String)作為參數(shù)撩嚼,并返回一個(gè)ArrayList, 其中包含推薦的啤酒品牌(String)
為模型構(gòu)建測(cè)試類
為模型創(chuàng)建測(cè)試類(在構(gòu)建模型本身之前先創(chuàng)建測(cè)試類)停士。剛開始測(cè)試模型時(shí),模型還在開發(fā)環(huán)境中完丽,與其他Java類一樣恋技,此時(shí)無(wú)需啟動(dòng)Tomcat也能測(cè)試
作者代碼(我是在創(chuàng)建模型類后再創(chuàng)建測(cè)試類)
package com.example.model;
import java.util.*;
class BeerExpertTest{
public static void main(String[] args){
BeerExpert be=new BeerExpert();
List<String> testBrands1=be.getBrands("amber");
Iterator it1=testBrands1.iterator();
while(it1.hasNext()){
System.out.println("try1: "+it1.next());
}
System.out.println("----------------------");
List<String> testBrands2=be.getBrands("");
Iterator it2=testBrands2.iterator();
while(it2.hasNext()){
System.out.println("try2: "+it2.next());
}
}
}
命令行中運(yùn)行測(cè)試類(請(qǐng)?jiān)跇?gòu)建模型類后進(jìn)行此步)
運(yùn)行此步后,開發(fā)環(huán)境項(xiàng)目中的/classes/com/example/model中會(huì)生成兩個(gè).class文件(原來(lái)BeerExpert編譯的.class文件被更新)逻族。-classpath中的./src為測(cè)試類所依賴的模型類(com.example.model.BeerExpert)的查找路徑蜻底。原教材中無(wú)此步,可略過(guò)聘鳞。
可以切換至測(cè)試類所在包的基目錄薄辅,然后運(yùn)行java命令
也可以直接在beerV1/下設(shè)置環(huán)境變量為classes目錄
構(gòu)建和測(cè)試模型
(原書代碼,增加了List泛型為String類)
package com.example.model;
import java.util.*;
public class BeerExpert{
public List<String> getBrands(String color){
List<String> brands = new ArrayList()<String>;
if (color.equals("amber")){
brands.add("Jack Amber");
brands.add("Red Moose");
}else{
brands.add("Jail Pale Ale");
brands.add("Gout Stout");
}
return(brands);
}
}
win系統(tǒng)與上圖操作命令一致抠璃,因?yàn)槟P皖愔袥](méi)有導(dǎo)入其他外部類长搀,所以無(wú)須設(shè)置classpath變量
改進(jìn)servlet,調(diào)用模型得到真正的建議
第2版的servlet中鸡典,通過(guò)改進(jìn)doPost()方法源请,調(diào)用模型來(lái)得到建議(第3版還會(huì)向JSP提供建議)
改進(jìn)servlet, 第2版
先把servlet放在一邊,只考慮Java
- 改進(jìn)doPost()方法來(lái)調(diào)用模型
- 編譯servlet
- 部署和測(cè)試更新后的Web應(yīng)用
(作者代碼,增加了List泛型部分谁尸,作者改用增強(qiáng)for循環(huán)遍歷集合舅踪,也可采用原書中的iterator迭代器遍歷)
import com.example.model;
public class BeerSelect2 extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("Beer selection Advice<br>");
String c = request.getParameter("color");
BeerExpert be = new BeerExpert();
List<String> advisedBrands= be.getBrands(c);
for(String ad:advisedBrands){
out.print("<br>try:"+ad);
}
}
}
注:原書中每次修改都替換原始的servlet, 作者采用創(chuàng)建新的servlet方式,以便區(qū)分不同版本的servlet——此方式需要在xml配置文件中相應(yīng)修改servlet類完全限定名
第2版Servlet代碼
package com.example.web;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import com.example.model.*;
import java.util.*;
public class BeerSelect2 extends HttpServlet{
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("Beer selection Advice<br>");
String c = request.getParameter("color");
BeerExpert be = new BeerExpert();
List<String> advisedBrands= be.getBrands(c);
for(String ad:advisedBrands){
out.print("<br>try:"+ad);
}
}
}
第2版servlet的關(guān)鍵步驟
主要有兩件事要做:重新編譯servlet和部署模型類
編譯servlet
win系統(tǒng)下
注:第2版servlet與前一版不同之處在于調(diào)用了模型良蛮,故在classpath環(huán)境變量中添加模型類的路徑
部署和測(cè)試Web應(yīng)用
- 把servlet.class文件的一個(gè)副本移到以下位置:
../Beer-v1/WEB-INF/classes/com/example/web/
這會(huì)替換第1版的servlet類文件
- 把模型的.class文件副本移到:
../Beer-v1/WEB-INF/classes/com/example/model/
- 關(guān)閉并重啟tomcat
- 通過(guò)form.html測(cè)試這個(gè)應(yīng)用
創(chuàng)建提供建議的JSP”視圖”
<!--這是一個(gè)“頁(yè)面指令”-->
<%@ page import="java.util.*" %>
<html>
<body>
<!--一些標(biāo)準(zhǔn)HTML模板-->
<h1 align="center">
Beer Recommendations JSP
</h1>
<br>
<!--以下稱為scriptlet代碼抽碌,用于輸入java代碼-->
<%
//這里從請(qǐng)求對(duì)象得到一個(gè)屬性
List styles=(List)request.getAttribute("styles");
Iterator it = styles.iterator();
while(it.hasNext()){
out.print("<br>try:"+it.next());
}
%>
</body>
</html>
部署JSP
不用編譯JSP(這個(gè)工作會(huì)在第一個(gè)請(qǐng)求到達(dá)容器時(shí)由容器完成)
- 把它命名為“result.jsp"
- 保存再開發(fā)環(huán)境的/web/中
- 將其副本移到部署環(huán)境的/Beer-v1/中
改進(jìn)這個(gè)servlet,讓它”調(diào)用“JSP(第3版)
這一步决瞳,我們要把servlet修改為”調(diào)用“JSP來(lái)生成輸出(視圖)货徙。容器提供了一種稱為”請(qǐng)求分派“的機(jī)制,允許容器管理一個(gè)組件調(diào)用另一個(gè)組件皮胡。我們通過(guò)使用這種機(jī)制痴颊,servlet從模型中得到信息,把它保存在請(qǐng)求對(duì)象中屡贺,然后把請(qǐng)求分派給JSP蠢棱。
必須對(duì)這個(gè)servlet做的重要修改:
- 把模型組件的回答增加到請(qǐng)求對(duì)象,以便JSP訪問(wèn)
- 要求容器把請(qǐng)求轉(zhuǎn)發(fā)給”result.jsp"
第3版servlet的代碼
如下修改servlet, 將模型組件的回答增加到請(qǐng)求對(duì)象(以便JSP獲人φ弧)泻仙,并要求容器把請(qǐng)求分派給JSP
(作者代碼,修改了類名和泛型量没,需要在xml文件修改配置)
package com.example.web;
import com.example.model.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class BeerSelect3 extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
String c = request.getParameter("color");
BeerExpert be = new BeerExpert();
List<String> result = be.getBrands(c);
//為請(qǐng)求對(duì)象增加一個(gè)屬性玉转,供JSP使用。注意殴蹄,JSP要尋找"styles"
request.setAttribute("styles", result);
//為JSP實(shí)例化一個(gè)請(qǐng)求分派器
RequestDispatcher view = request.getRequestDispatcher("result.jsp");
//使用請(qǐng)求分派器要求容器準(zhǔn)備好JSP,并向JSP發(fā)送請(qǐng)求和響應(yīng)
view.forward(request,response);
}
}
編譯冤吨、部署和測(cè)試最后的應(yīng)用
編譯servlet
部署和測(cè)試Web應(yīng)用
- 把servlet的.class文件副本移到../Beer-v1/WEB-INF/classes/com/example/web/
- 關(guān)閉并重啟tomcat
- 通過(guò)form.html測(cè)試應(yīng)用