本文节选自《Head First Servlets and JSP》第3章 MVC实战,根据个人配置和实操情况有所删改。本人注解部分用斜体表示。图片截取自原书,部分为个人操作截图。本人电脑配置win10x64, tomcat7, jdk10。本教程的特色处在于全程采用手工编写和命令行结合,不借助IDE开发环境,适合初次接触Servlet者当作练习的小项目。任何问题请在下方留言。
(基于Beer Advisor的案例。根据用户选择的酒色提供酒的品牌建议的servlet小程序)
IDE项目的目录结构
将Web项目部署到容器中
form.html包含标题文本,一个下拉列表,还有一个提交按钮(原书代码)
<html>
<body>
<h1 align="center">
Beer Selection Page
h1>
<form method="POST" action="SelectBeer.do">
<p>
Select beer characteristics
p>
Color:
<select name="color" size="1">
<option value="light">lightoption>
<option value="amber">amberoption>
<option value="brown">brownoption>
<option value="dark">darkoption>
select>
<br><br>
<center>
<input type="SUBMIT">
center>
form>
body>
html>
创建这个HTML文件,取名为form.html,然后保存在开发环境的/beerV1/web/目录下
把form.html文件的一个副本放在tomcat/webapps/Beer-vl/中
创建XML文档,取名为web.xml, 把它保存在开发环境的/beer/etc/目录下(原书代码)
<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>
<servlet-name>Ch3 Beerservlet-name>
<servlet-class>com.example.web.BeerSelectservlet-class>
servlet>
<servlet-mapping>
<servlet-name>Ch3 Beerservlet-name>
<url-pattern>/SelectBeer.dourl-pattern>
servlet-mapping>
web-app>
由于原书代码是tomcat5, 版本有所变化,可以到tomcat/webapps/ROOT/WEB-INF自带目录下拷贝了一份web-xml文件,并添加相应的映射语句。
把web.xml文件的一个副本放在tomcat/webapps/Beer-v1/WEB-INF/
Tomcat既作为Web服务器,又作为Web容器。要启动Tomcat, 先用cd命令切换到tomcat主目录,再运行bin/startup.sh
命令行中用cd切换到tomcat/bin目录下(tomcat根据版本名称不同),win系统运行startup.bat
在浏览器中打开这个HTML页面,为此键入:
http://localhost:8080/Beer-v1/form.html
以下部分为xml文件中配置的详解
/1Beer-v1[^2]/SelectBeer.do[^3]
在用户发送的http post请求中,“/Beer-v1”不是路径的一部分。在form.html中,它只说:
但浏览器为请求追加了“/Beer-v1/”,因为用户请求就来自这里。换句话说,form.html中的“SelectBeer.do”相对于其所在页面的URL。在这里,就是相对于Web应用的根:“/Beer-v1”
与/SelectBeer.do匹配的一个
, 这里的斜线{/}表示Web应用的上下文根,SelectBeer.do就是资源的逻辑名
的
是“Ch3 Beer”。但是这并不是实际servlet类文件的名字。“Ch3 Beer”是servlet名,而不是servlet类的名字。对容器来说,servlet只是在DD中
标记下的一个东西。servlet名只在DD中使用,以便DD的其他部分建立与该servlet的映射
为“Ch3 Beer”的
标记
标记中的
,容器可以知道有哪个servlet类负责处理这个请求。如果这个servlet还没有初始化,就会加载类,并初始化servlet确保HTML页面能适当地调用servlet, 而且servlet能正确地接收HTML参数。
(原书代码)
//确保与前面创建的开发结构和部署结构匹配
package com.example.web;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
//HttpServlet扩展了GenericServlet,GenericServlet则实现了Servlet接口
public class BeerSelect extends HttpServlet{
//我们使用doPOST来处理HTTP请求,因为HTML表单指出,method=POST
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
//这个方法来自ServletResponse接口
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("Beer Selection Advice
");
//这个方法来自ServletRequest接口。注意这个参数与HTML
String c = request.getParameter("color");
//这里我们没有返回建议,只是把测试信息显示出来
out.println("
Got beer color "+c);
}
}
编译servlet
用-d标志编译servlet,把类放在开发环境中
原书
win系统
注解:-classpath用于设置临时环境变量,指定查找用户类文件和注释处理程序的位置;本例中由于编译servlet需要用到额外类库,tomcat提供了这些jar包,故选定tomcat/lib/servlet-api.jar为环境变量。书中还添加了classes和”.”(代表当前目录)作为路径,其实可以略去。
-d用于指定编译生成的class文件存放目录,本例中存放于classes路径中,由于servlet的package语句,会自动生成com.example.web目录(若不存在)。
最后,添上servlet类的存放路径,若存放在当前目录则直接输入文件名。
部署servlet
要部署servlet, 建立.class文件的一个副本,并把它移到部署结构的/Beer-v1/WEB-INF/classes/com/example/web/目录下
测试
直接在tomcat/bin目录下输入startup.bat命令即可重启,无须关闭
http://localhost:8080/Beer-v1/form.html
Beer Selection Advice
Got beer color brown
模型规范
为模型构建测试类
为模型创建测试类(在构建模型本身之前先创建测试类)。刚开始测试模型时,模型还在开发环境中,与其他Java类一样,此时无需启动Tomcat也能测试
作者代码(我是在创建模型类后再创建测试类)
package com.example.model;
import java.util.*;
class BeerExpertTest{
public static void main(String[] args){
BeerExpert be=new BeerExpert();
List testBrands1=be.getBrands("amber");
Iterator it1=testBrands1.iterator();
while(it1.hasNext()){
System.out.println("try1: "+it1.next());
}
System.out.println("----------------------");
List testBrands2=be.getBrands("");
Iterator it2=testBrands2.iterator();
while(it2.hasNext()){
System.out.println("try2: "+it2.next());
}
}
}
命令行中运行测试类(请在构建模型类后进行此步)
运行此步后,开发环境项目中的/classes/com/example/model中会生成两个.class文件(原来BeerExpert编译的.class文件被更新)。-classpath中的./src为测试类所依赖的模型类(com.example.model.BeerExpert)的查找路径。原教材中无此步,可略过。
也可以直接在beerV1/下设置环境变量为classes目录
构建和测试模型
(原书代码,增加了List泛型为String类)
package com.example.model;
import java.util.*;
public class BeerExpert{
public List getBrands(String color){
List brands = new ArrayList();
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系统与上图操作命令一致,因为模型类中没有导入其他外部类,所以无须设置classpath变量
第2版的servlet中,通过改进doPost()方法,调用模型来得到建议(第3版还会向JSP提供建议)
改进servlet, 第2版
先把servlet放在一边,只考虑Java
(作者代码,增加了List泛型部分,作者改用增强for循环遍历集合,也可采用原书中的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
");
String c = request.getParameter("color");
BeerExpert be = new BeerExpert();
List advisedBrands= be.getBrands(c);
for(String ad:advisedBrands){
out.print("
try:"+ad);
}
}
}
注:原书中每次修改都替换原始的servlet, 作者采用创建新的servlet方式,以便区分不同版本的servlet——此方式需要在xml配置文件中相应修改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
");
String c = request.getParameter("color");
BeerExpert be = new BeerExpert();
List advisedBrands= be.getBrands(c);
for(String ad:advisedBrands){
out.print("
try:"+ad);
}
}
}
主要有两件事要做:重新编译servlet和部署模型类
编译servlet
win系统下
注:第2版servlet与前一版不同之处在于调用了模型,故在classpath环境变量中添加模型类的路径
部署和测试Web应用
../Beer-v1/WEB-INF/classes/com/example/web/
这会替换第1版的servlet类文件
../Beer-v1/WEB-INF/classes/com/example/model/
<%@ page import="java.util.*" %>
<html>
<body>
<h1 align="center">
Beer Recommendations JSP
h1>
<br>
<%
//这里从请求对象得到一个属性
List styles=(List)request.getAttribute("styles");
Iterator it = styles.iterator();
while(it.hasNext()){
out.print("
try:"+it.next());
}
%>
body>
html>
部署JSP
不用编译JSP(这个工作会在第一个请求到达容器时由容器完成)
这一步,我们要把servlet修改为”调用“JSP来生成输出(视图)。容器提供了一种称为”请求分派“的机制,允许容器管理一个组件调用另一个组件。我们通过使用这种机制,servlet从模型中得到信息,把它保存在请求对象中,然后把请求分派给JSP。
必须对这个servlet做的重要修改:
如下修改servlet, 将模型组件的回答增加到请求对象(以便JSP获取),并要求容器把请求分派给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 result = be.getBrands(c);
//为请求对象增加一个属性,供JSP使用。注意,JSP要寻找"styles"
request.setAttribute("styles", result);
//为JSP实例化一个请求分派器
RequestDispatcher view = request.getRequestDispatcher("result.jsp");
//使用请求分派器要求容器准备好JSP,并向JSP发送请求和响应
view.forward(request,response);
}
}
编译servlet
部署和测试Web应用