Java Servlet和JSP教程
简介 1.1 Java Servlet及其特点 1.2 JSP及其特点
2.1 安装Servlet和JSP开发工具 下面的代码显示了一个简单Servlet的基本结构。该Servlet处理的是GET请求,所谓的GET请求,如果你不熟悉HTTP,可以把它看成是当用户在浏览器地址栏输入URL、点击Web页面中的链接、提交没有指定METHOD的表单时浏览器所发出的请求。Servlet也可以很方便地处理POST请求。POST请求是提交那些指定了METHOD=“POST”的表单时所发出的请求,具体请参见稍后几节的讨论。 import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class SomeServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 使用“request”读取和请求有关的信息(比如Cookies) // 和表单数据 // 使用“response”指定HTTP应答状态代码和应答头 // (比如指定内容类型,设置Cookie) PrintWriter out = response.getWriter(); // 使用 "out"把应答内容发送到浏览器 } } 如果某个类要成为Servlet,则它应该从HttpServlet 继承,根据数据是通过GET还是POST发送,覆盖doGet、doPost方法之一或全部。doGet和doPost方法都有两个参数,分别为HttpServletRequest 类型和HttpServletResponse 类型。HttpServletRequest提供访问有关请求的信息的方法,例如表单数据、HTTP请求头等等。HttpServletResponse除了提供用于指定HTTP应答状态(200,404等)、应答头(Content-Type,Set-Cookie等)的方法之外,最重要的是它提供了一个用于向客户端发送数据的PrintWriter 。对于简单的Servlet来说,它的大部分工作是通过println语句生成向客户端发送的页面。 注意doGet和doPost抛出两个异常,因此你必须在声明中包含它们。另外,你还必须导入java.io包(要用到PrintWriter等类)、javax.servlet包(要用到HttpServlet等类)以及javax.servlet.http包(要用到HttpServletRequest类和HttpServletResponse类)。 最后,doGet和doPost这两个方法是由service方法调用的,有时你可能需要直接覆盖service方法,比如Servlet要处理GET和POST两种请求时。 3.2 输出纯文本的简单Servlet 下面是一个输出纯文本的简单Servlet。 3.2.1 HelloWorld.java package hall; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class HelloWorld extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println("Hello World"); } } 3.2.2 Servlet的编译和安装 不同的Web服务器上安装Servlet的具体细节可能不同,请参考Web服务器文档了解更权威的说明。假定使用Java Web Server(JWS)2.0,则Servlet应该安装到JWS安装目录的servlets子目录下。在本文中,为了避免同一服务器上不同用户的Servlet命名冲突,我们把所有Servlet都放入一个独立的包hall中;如果你和其他人共用一个服务器,而且该服务器没有“虚拟服务器”机制来避免这种命名冲突,那么最好也使用包。把Servlet放入了包hall之后,HelloWorld.java实际上是放在servlets目录的hall子目录下。 大多数其他服务器的配置方法也相似,除了JWS之外,本文的Servlet和JSP示例已经在BEA WebLogic和IBM WebSphere 3.0下经过测试。WebSphere具有优秀的虚拟服务器机制,因此,如果只是为了避免命名冲突的话并非一定要用包。 对于没有使用过包的初学者,下面我们介绍编译包里面的类的两种方法。 一种方法是设置CLASSPATH,使其指向实际存放Servlet的目录的上一级目录(Servlet主目录),然后在该目录中按正常的方式编译。例如,如果Servlet的主目录是C:\JavaWebServer\servlets,包的名字(即主目录下的子目录名字)是hall,在Windows下,编译过程如下: DOS> set CLASSPATH=C:\JavaWebServer\servlets;%CLASSPATH% DOS> cd C:\JavaWebServer\servlets\hall DOS> javac YourServlet.java 第二种编译包里面的Servlet的方法是进入Servlet主目录,执行“javac directory\YourServlet.java”(Windows)或者“javac directory/YourServlet.java”(Unix)。例如,再次假定Servlet主目录是C:\JavaWebServer\servlets,包的名字是hall,在Windows中编译过程如下: DOS> cd C:\JavaWebServer\servlets DOS> javac hall\YourServlet.java 注意在Windows下,大多数JDK 1.1版本的javac要求目录名字后面加反斜杠(\)。JDK1.2已经改正这个问题,然而由于许多Web服务器仍旧使用JDK 1.1,因此大量的Servlet开发者仍旧在使用JDK 1.1。 最后,Javac还有一个高级选项用于支持源代码和.class文件的分开放置,即你可以用javac的“-d”选项把.class文件安装到Web服务器所要求的目录。 3.2.3 运行Servlet 在Java Web Server下,Servlet应该放到JWS安装目录的servlets子目录下,而调用Servlet的URL是http://host/servlet/ServletName。注意子目录的名字是servlets(带“s”),而URL使用的是“servlet”。由于HelloWorld Servlet放入包hall,因此调用它的URL应该是http://host/servlet/hall.HelloWorld。在其他的服务器上,安装和调用Servlet的方法可能略有不同。 大多数Web服务器还允许定义Servlet的别名,因此Servlet也可能用http://host/any-path/any-file.html形式的URL调用。具体如何进行配置完全依赖于服务器类型,请参考服务器文档了解细节。 3.3 输出HTML的Servlet 大多数Servlet都输出HTML,而不象上例一样输出纯文本。要输出HTML还有两个额外的步骤要做:告诉浏览器接下来发送的是HTML;修改println语句构造出合法的HTML页面。 第一步通过设置Content-Type(内容类型)应答头完成。一般地,应答头可以通过HttpServletResponse的setHeader方法设置,但由于设置内容类型是一个很频繁的操作,因此Servlet API提供了一个专用的方法setContentType。注意设置应答头应该在通过PrintWriter发送内容之前进行。下面是一个实例: HelloWWW.java package hall; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class HelloWWW extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " + "Transitional//EN\">\n" + "<HTML>\n" + "<HEAD><TITLE>Hello WWW</TITLE></HEAD>\n" + "<BODY>\n" + "<H1>Hello WWW</H1>\n" + "</BODY></HTML>"); } } 3.4 几个HTML工具函数 通过println语句输出HTML并不方便,根本的解决方法是使用JavaServer Pages(JSP)。然而,对于标准的Servlet来说,由于Web页面中有两个部分(DOCTYPE和HEAD)一般不会改变,因此可以用工具函数来封装生成这些内容的代码。 虽然大多数主流浏览器都会忽略DOCTYPE行,但严格地说HTML规范是要求有DOCTYPE行的,它有助于HTML语法检查器根据所声明的HTML版本检查HTML文档合法性。在许多Web页面中,HEAD部分只包含<TITLE>。虽然许多有经验的编写者都会在HEAD中包含许多META标记和样式声明,但这里只考虑最简单的情况。 下面的Java方法只接受页面标题为参数,然后输出页面的DOCTYPE、HEAD、TITLE部分。清单如下: ServletUtilities.java package hall; public class ServletUtilities { public static final String DOCTYPE = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">"; public static String headWithTitle(String title) { return(DOCTYPE + "\n" + "<HTML>\n" + "<HEAD><TITLE>" + title + "</TITLE></HEAD>\n"); } // 其他工具函数的代码在本文后面介绍 } HelloWWW2.java 下面是应用了ServletUtilities之后重写HelloWWW类得到的HelloWWW2: package hall; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class HelloWWW2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println(ServletUtilities.headWithTitle("Hello WWW") + "<BODY>\n" + "<H1>Hello WWW</H1>\n" + "</BODY></HTML>"); } } 4.1 表单数据概述 如果你曾经使用过Web搜索引擎,或者浏览过在线书店、股票价格、机票信息,或许会留意到一些古怪的URL,比如“http://host/path?user=Marty+Hall&;origin=bwi&dest=lax”。这个URL中位于问号后面的部分,即“user=Marty+Hall&origin=bwi&dest=lax”,就是表单数据,这是将Web页面数据发送给服务器程序的最常用方法。对于GET请求,表单数据附加到URL的问号后面(如上例所示);对于POST请求,表单数据用一个单独的行发送给服务器。 以前,从这种形式的数据提取出所需要的表单变量是CGI编程中最麻烦的事情之一。首先,GET请求和POST请求的数据提取方法不同:对于GET请求,通常要通过QUERY_STRING环境变量提取数据;对于POST请求,则一般通过标准输入提取数据。第二,程序员必须负责在“&”符号处截断变量名字-变量值对,再分离出变量名字(等号左边)和变量值(等号右边)。第三,必须对变量值进行URL反编码操作。因为发送数据的时候,字母和数字以原来的形式发送,但空格被转换成加号,其他字符被转换成“%XX”形式,其中XX是十六进制表示的字符ASCII(或者ISO Latin-1)编码值。例如,如果HTML表单中名为“users”的域值为“~hall, ~gates, and ~mcnealy”,则实际向服务器发送的数据为“users=%7Ehall%2C+%7Egates%2C+and+%7Emcnealy”。最后,即第四个导致解析表单数据非常困难的原因在于,变量值既可能被省略(如“param1=val1&param2=&param3=val3”),也有可能一个变量拥有一个以上的值,即同一个变量可能出现一次以上(如“param1=val1&param2=val2&param1=val3”)。 Java Servlet的好处之一就在于所有上述解析操作都能够自动完成。只需要简单地调用一下HttpServletRequest的getParameter方法、在调用参数中提供表单变量的名字(大小写敏感)即可,而且GET请求和POST请求的处理方法完全相同。 getParameter方法的返回值是一个字符串,它是参数中指定的变量名字第一次出现所对应的值经反编码得到得字符串(可以直接使用)。如果指定的表单变量存在,但没有值,getParameter返回空字符串;如果指定的表单变量不存在,则返回null。如果表单变量可能对应多个值,可以用getParameterValues来取代getParameter。getParameterValues能够返回一个字符串数组。 最后,虽然在实际应用中Servlet很可能只会用到那些已知名字的表单变量,但在调试环境中,获得完整的表单变量名字列表往往是很有用的,利用getParamerterNames方法可以方便地实现这一点。getParamerterNames返回的是一个Enumeration,其中的每一项都可以转换为调用getParameter的字符串。 4.2 实例:读取三个表单变量 下面是一个简单的例子,它读取三个表单变量param1、param2和param3,并以HTML列表的形式列出它们的值。请注意,虽然在发送应答内容之前必须指定应答类型(包括内容类型、状态以及其他HTTP头信息),但Servlet对何时读取请求内容却没有什么要求。 另外,我们也可以很容易地把Servlet做成既能处理GET请求,也能够处理POST请求,这只需要在doPost方法中调用doGet方法,或者覆盖service方法(service方法调用doGet、doPost、doHead等方法)。在实际编程中这是一种标准的方法,因为它只需要很少的额外工作,却能够增加客户端编码的灵活性。 如果你习惯用传统的CGI方法,通过标准输入读取POST数据,那么在Servlet中也有类似的方法,即在HttpServletRequest上调用getReader或者getInputStream,但这种方法对普通的表单变量来说太麻烦。然而,如果是要上载文件,或者POST数据是通过专门的客户程序而不是HTML表单发送,那么就要用到这种方法。 注意用第二种方法读取POST数据时,不能再用getParameter来读取这些数据。 ThreeParams.java package hall; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.util.*; public class ThreeParams extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); String title = "读取三个请求参数"; out.println(ServletUtilities.headWithTitle(title) + "<BODY>\n" + "<H1 ALIGN=CENTER>" + title + "</H1>\n" + "<UL>\n" + " <LI>param1: " + request.getParameter("param1") + "\n" + " <LI>param2: " + request.getParameter("param2") + "\n" + " <LI>param3: " + request.getParameter("param3") + "\n" + "</UL>\n" + "</BODY></HTML>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 4.3 实例:输出所有的表单数据 下面这个例子寻找表单所发送的所有变量名字,并把它们放入表格中,没有值或者有多个值的变量都突出显示。 首先,程序通过HttpServletRequest的getParameterNames方法得到所有的变量名字,getParameterNames返回的是一个Enumeration。接下来,程序用循环遍历这个Enumeration,通过hasMoreElements确定何时结束循环,利用nextElement得到Enumeration中的各个项。由于nextElement返回的是一个Object,程序把它转换成字符串后再用这个字符串来调用getParameterValues。 getParameterValues返回一个字符串数组,如果这个数组只有一个元素且等于空字符串,说明这个表单变量没有值,Servlet以斜体形式输出“No Value”;如果数组元素个数大于1,说明这个表单变量有多个值,Servlet以HTML列表形式输出这些值;其他情况下Servlet直接把变量值放入表格。 ShowParameters.java 注意,ShowParameters.java用到了前面介绍过的ServletUtilities.java。 package hall; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.util.*; public class ShowParameters extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); String title = "读取所有请求参数"; out.println(ServletUtilities.headWithTitle(title) + "<BODY BGCOLOR=\"#FDF5E6\">\n" + "<H1 ALIGN=CENTER>" + title + "</H1>\n" + "<TABLE BORDER=1 ALIGN=CENTER>\n" + "<TR BGCOLOR=\"#FFAD00\">\n" + "<TH>参数名字<TH>参数值"); Enumeration paramNames = request.getParameterNames(); while(paramNames.hasMoreElements()) { String paramName = (String)paramNames.nextElement(); out.println("<TR><TD>" + paramName + "\n<TD>"); String[] paramValues = request.getParameterValues(paramName); if (paramValues.length == 1) { String paramValue = paramValues[0]; if (paramValue.length() == 0) out.print("<I>No Value</I>"); else out.print(paramValue); } else { out.println("<UL>"); for(int i=0; i<paramValues.length; i++) { out.println("<LI>" + paramValues[i]); } out.println("</UL>"); } } out.println("</TABLE>\n</BODY></HTML>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 测试表单 下面是向上述Servlet发送数据的表单PostForm.html。就像所有包含密码输入域的表单一样,该表单用POST方法发送数据。我们可以看到,在Servlet中同时实现doGet和doPost这两种方法为表单制作带来了方便。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE>示例表单</TITLE> </HEAD> <BODY BGCOLOR="#FDF5E6"> <H1 ALIGN="CENTER">用POST方法发送数据的表单</H1> <FORM ACTION="/servlet/hall.ShowParameters" METHOD="POST"> Item Number: <INPUT TYPE="TEXT" NAME="itemNum"><BR> Quantity: <INPUT TYPE="TEXT" NAME="quantity"><BR> Price Each: <INPUT TYPE="TEXT" NAME="price" VALUE="$"><BR> <HR> First Name: <INPUT TYPE="TEXT" NAME="firstName"><BR> Last Name: <INPUT TYPE="TEXT" NAME="lastName"><BR> Middle Initial: <INPUT TYPE="TEXT" NAME="initial"><BR> Shipping Address: <TEXTAREA NAME="address" ROWS=3 COLS=40></TEXTAREA><BR> Credit Card:<BR> <INPUT TYPE="RADIO" NAME="cardType" VALUE="Visa">Visa<BR> <INPUT TYPE="RADIO" NAME="cardType" VALUE="Master Card">Master Card<BR> <INPUT TYPE="RADIO" NAME="cardType" VALUE="Amex">American Express<BR> <INPUT TYPE="RADIO" NAME="cardType" VALUE="Discover">Discover<BR> <INPUT TYPE="RADIO" NAME="cardType" VALUE="Java SmartCard">Java SmartCard<BR> Credit Card Number: <INPUT TYPE="PASSWORD" NAME="cardNum"><BR> Repeat Credit Card Number: <INPUT TYPE="PASSWORD" NAME="cardNum"><BR><BR> <CENTER> <INPUT TYPE="SUBMIT" VALUE="Submit Order"> </CENTER> </FORM> </BODY> </HTML>
5.1 HTTP请求头概述
6.1 CGI变量概述 Web服务器响应浏览器或其他客户程序的请求时,其应答一般由以下几个部分组成:一个状态行,几个应答头,一个空行,内容文档。下面是一个最简单的应答: HTTP/1.1 200 OK Content-Type: text/plain Hello World 状态行包含HTTP版本、状态代码、与状态代码对应的简短说明信息。在大多数情况下,除了Content-Type之外的所有应答头都是可选的。但Content-Type是必需的,它描述的是后面文档的MIME类型。虽然大多数应答都包含一个文档,但也有一些不包含,例如对HEAD请求的应答永远不会附带文档。有许多状态代码实际上用来标识一次失败的请求,这些应答也不包含文档(或只包含一个简短的错误信息说明)。 Servlet可以利用状态代码来实现许多功能。例如,可以把用户重定向到另一个网站;可以指示出后面的文档是图片、PDF文件或HTML文件;可以告诉用户必须提供密码才能访问文档;等等。这一部分我们将具体讨论各种状态代码的含义以及利用这些代码可以做些什么。 7.2 设置状态代码 如前所述,HTTP应答状态行包含HTTP版本、状态代码和对应的状态信息。由于状态信息直接和状态代码相关,而HTTP版本又由服务器确定,因此需要Servlet设置的只有一个状态代码。 Servlet设置状态代码一般使用HttpServletResponse的setStatus方法。setStatus方法的参数是一个整数(即状态代码),不过为了使得代码具有更好的可读性,可以用HttpServletResponse中定义的常量来避免直接使用整数。这些常量根据HTTP 1.1中的标准状态信息命名,所有的名字都加上了SC前缀(Status Code的缩写)并大写,同时把空格转换成了下划线。也就是说,与状态代码404对应的状态信息是“Not Found”,则HttpServletResponse中的对应常量名字为SC_NOT_FOUND。但有两个例外:和状态代码302对应的常量根据HTTP 1.0命名,而307没有对应的常量。 设置状态代码并非总是意味着不要再返回文档。例如,虽然大多数服务器返回404应答时会输出简单的“File Not Found”信息,但Servlet也可以定制这个应答。不过,定制应答时应当在通过PrintWriter发送任何内容之前先调用response.setStatus。 虽然设置状态代码一般使用的是response.setStauts(int)方法,但为了简单起见,HttpServletResponse为两种常见的情形提供了专用方法:sendError方法生成一个404应答,同时生成一个简短的HTML错误信息文档;sendRedirect方法生成一个302应答,同时在Location头中指示新文档的URL。 |