第 2 章 Servlet 编程
Java Servlet是运行在Web服务器或应用服务器上的程序,它是来自Web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层。使用Servlet,可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。本章主要内容有: (1) Servlet简介; (2) Servlet 基础;(3) Servlet API编程常用接口和类;(4) Servlet处理表单数据: (5) Servlet重定向和请求转发; ( 6) Servlet数据库访问; ( 7) Servlet异常处理;(8)异步Servlet。
2.1 Servlet 简介
Servlet在Web服务器的地址空间内执行。这样,其没有必要再创建一个单独的进程来处理每个客户端请求。在传统的CGI中,每个请求都要启动一个新的进程,如果CGI程序本身的执行时间较短,则启动进程所需要的开销很可能反而超过了实际执行时间。而在Servlet中,每个请求由一个轻量级的Java线程(而不是重量级的操作系统进程)处理。
在传统CGI中,如果有N个并发的对同一CGI程序的请求,则该CGI程序的代码在内存中重复装载了N次;而对于Servlet,处理请求的是N个线程,只需要一份Servlet类代码。在性能优化方面,Servlet 也比CGI有着更多的选择。
Servlet是独立于平台的,因为它们是用Java编写的。
Java类库的全部功能对Servlet来说都是可用的。
Servlet对请求的处理和响应过程可分为以下几个步骤。
(1)客户端发送请求至服务器端。
(2)服务器将请求信息发送至Servlet。
(3) Servlet 生成响应内容并将其传给服务器。响应内容动态生成,通常取决于客户端的请求。
(4)服务器将响应返回给客户端。
- Servlet 的存在就是要为客户端服务
Servlet的任务是得到一个客户端的请求,再发回一个响应。请求可能很简单一“ 请给我一个欢迎页面。”,也可能很复杂一“ 为我的购物车结账。”这个请求携带着一些重要的数据,Servlet 代码必须知道怎么找到和使用这个请求。响应也携带着些一信息,浏览器需要这些信息来显示一个页面,Servlet 代码必须知道怎么发送这些信息。 - Servlet 3.0
Servlet 3.0作为JavaEE 6规范体系中的一员,随着Java EE6规范起一发布。该版本在前版本(Servlet2.5)的基础上提供了若干新特性,以用于简化Web应用的开发和部署。
(1)异步处理支持:有了该特性,Servlet 线程不再需要一直阻塞, 直到业务处理完毕才能再输出响应,最后才能结束该Servlet线程。在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,自已在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。
(2)新增的注解支持:该版本新增了若干注解,用于简化Servlet、过滤器(Filter) 和监听器(Listener) 的声明,这使得Web.xml部署描述文件从该版本开始不再是必选的了。
- Servlet 与Tomcat版本支持
以目前主流的Web服务器Tomcat (包含Servlet容器)为例,Tomcat对Servlet版本的支持关系如表2-1所示。
表2-1 Servlet与Tomcat版本支持
Servlet 版本号 | Tomcat 版本号 | Servlet版本号 | Tomcat版本号 |
Servlet2.4 | Tomcat5.x | Servlet3.0 | Tomcat7.x |
Servlet2.4 | Tomcat6.x | Servlet3.1 | Tomcat8.x |
2.2 Servlet 基础
2.2.1 用记事本写一个 Servlet
- 编写HelloServlet.java
使用记事本编写HelloServlet.java 文件,代码如下:
import javax.servlet.ServletException; import java.io.*; import javax.servlet.http.*; public class HelloServlet extends HttpServlet { //重写了父类HttpServlet中的doGet()方法,用于对GET请求方法做出响应 public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException { PrintWriter out = resp.getWriter(); //得到PrintWriter对象 out.println("Hello Servlet"); //向客户端发送字符数据 out.close(); } }
- 编译HelloSerlet.java
把Tomcat自带的servlet-api.jar的路径加到classpath中,或者在命令行窗口中运行命令:
set classpath=%classpath%;D:\software\apache-tomcat-8.0.45\lib\servlet-api.jar
(这里,本书Tomcat的安装路径是E:\tormcat8。)
进入HelloServlet.java 所在目录,运行命令: javac HelloServlet.java 。 编译后得到HelloServlet.class字节码文件。
- 部署Servlet
在E:\tomcat8\webapps目录下,创建-一个文件夹hello,在hello目录下创建WEB-INF文件夹和Web.xml文件。Web.xml 代码如下(其中 部分表示注释):
<?xml version="1.0" encoding="UTF-8"?> <!--XML声明--> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <!--声明XML Schema的名称空间--> <servlet> <servlet-name>helloworld</servlet-name> <!--Servlet名称--> <servlet-class>HelloServlet</servlet-class> <!--Servlet完全限定名--> </servlet> <servlet-mapping> <servlet-name>helloworld</servlet-name> <!--Servlet名称--> <url-pattern>/myhello</url-pattern> <!--Servlet的URL路径--> </servlet-mapping> </web-app>
在Web-INF目录下创建classes 文件夹,把前面编译得到的HelloServlet.class 复制至E:\tomcat8\webapps\hello\Web-INF\classes目录下。
在这里,hello 文件夹已成为一个Web应用,只不过是手工创建的。
- 访问HelloServlet
启动Tomcat服务器,在浏览器中输入: http://localhost:8080/hello/myhello (注意,这里通过调用中的值来找寻相应的Servlet。)
网页输出:
Hello Servlet
2.2.2 Servlet 体系结构
Servlet3.1 API有以下4个Java包。
①javax.servlet: 其中包含定义Servlet和Servlet容器之间契约的类和接口。
②javax.servlet.http: 其中包含定义HTTP Servlet和Servlet容器之间契约的类和接口。
③javax.servlet.annotation:其中包含标注Servlet、 Filter、 Listener 的标注。它还为被标注元件定义元数据。
④javax.servlet.descriptor:其中包含提供程序化登录Web应用程序的配置信息的类型。Servlet技术的核心是Servlet,它是所有Servlet类必须直接或间接实现的一个接口。Servlet接口定义了Servlet 与Servlet 容器之间的契约,Servlet 容器将Servlet 类载入内存,并在Servlet实例上调用具体的方法。用户请求致使Servlet容器调用Servlet的Service方法,并传入一个ServletRequest 实例和一个ServletResponse 实例。ServletRequest 中封装了当前的HTTP请求。ServletResponse则表示当前用户的HTTP响应,使得将响应发回给用户变得十分容易。
2.2.3 Servlet 接口
在Tomcat的有关Servlet API的文档中有关于Servlet接口的介绍。可以通过运行Tomcat服务器,在htp://localhost:8080/docs/servletapi/index.html页面看到。
javax.servlet.Servlet的定义如下:
public interface Servlet
该接口中定义了一下 5 种方法。
①(init): 在Servlet实例化后,Servlet 容器会调用该方法,初始化该对象。init0方法有一个类型为ServletConfig的参数, Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用ServletConfig 对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。
②service(): 容器调用service0方 法来处理客户端的请求。
③destroy0: Servlet 的销毁方法。容器在终止Servlet服务前调用此方法。
④getServletConfig0: 该方法返回容器调用init(方 法时传递给Servlet 对象的ServletConfig对象,ServletConfig 对象包含了Servlet 的初始化参数。
⑤getServletInf(): 返回一个String类型的字符串,其中包括了关于Servlet的信息。例如,作者、描述信息等。
2.2.4 Servlet 生命周期
Servlet生命周期可被定义为从创建直到毁灭的整个过程。以下是Servlet遵循的过程。
(1) Servlet 通过调用init ()方法进行初始化。
(2) Servlet 调用service()方法来处理客户端的请求。
(3) Servlet通过调用destroy()方法终止(结束)。
(4) Servlet 是由JVM的垃圾回收器进行垃圾回收的。
1. init() 方法
init()方法被设计成只调用一一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。Servlet创建于用户第一次调用对应于该Servlet的URL时,但也可以指定Servlet在服务器第一次启动时被加载。
当用户调用一个Servlet时,就会创建一个Servlet实例,每一个用户请求都会产生一个新的线程,适当的时候移交给doGet()或doPost()方法。init0方法简 单地创建或加载- -些数据,这些数据将被用于Servlet的整个生命周期。
init 方法如下:
public void init(ServletConfig config) throws ServletExeption { // 初始化代码... }
2. service() 方法
service()方法是执行实际任务的主要方法。Servlet 容器调用service0方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回客户端。每次服务器接收到一个Servlet请求时,服务器会产生一个新的线程并调用服务。servic()方法检查HTTP请求类型(GET、POST、PUT、DELETE等),根据请求类型不同分别调用doGet、doPost、 doPut、 doDelete 等方法。
public void service (ServletRequest request, ServletResponse response) throws ServletException, IOException { }
service()方法由容器调用,只需要根据来自客户端的请求类型来重写doGet()或doPost()方法即可。doGet())和 doPost()方法是每次服务请求中最常用的方法。
3. doGet() 方法
GET请求来自一个URL的正常请求,或者来自一个未指定METHOD的HTML表单,它由doGet()方法处理。
public void doGet (HttpServletRequestrequest, HttpServletResponse response) throws ServletException, IOException { // Servlet代码 }
4. doPost()方法
POST请求来自一个特别指定了METHOD为POST的HTML表单,它由doPost()方法处理。
public void doPost (HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { // Servlet代码 }
5. destroy() 方法
destroy()方法只会被调用一-次,在Servlet 生命周期结束时被调用。destroy()方法可以让Servlet关闭数据库连接、停止后台线程、把Cookie列表或单击计数器写入磁盘中,并执行其他类似的清理活动。在调用destroy()方法之后,servlet 对象被标记为垃圾回收。destroy()方法定义如下所示:
public void destroy () { //清理代码... }
2.2.5 Servlet 生命周期示例
Servlet实质,上就是Java类,我们将使用Eclipse来开发Servlet,以进行Servlet 的学习。
1. 创建Dynamic Web Project
在Eclipse中选择“File-→New→Projet…“选项,在弹出的“New Project” 对话框中选择Web中的"Dynamic Web Project",如图2-1所示。在弹出的“New Dynamic Web Project"对话框中,设定工程名为“ServletDemo",如图2-2所示(此处所使用的Eclipse版本详见本书资源tools文件夹下 eclpse-jee-oxygen-R-win32.zip,将其解压缩后双击eclipse.exe即)。
在如图2-3所示的ServletDemo工程目录结构中,右击src 文件夹,创建Package 名为““ com.mialab.servlet demo”再在com.mialab.servlet demo包中创建Servlet。
2. 开发Servlet
我们在com.mialab.servlet_demo包中创建LifeServlet。右击com.mialab.servlet _demo包,选择“New- +Servlet"选项,弹出“Create Servlet"对话框,在此对话框中输入Servlet的名称"LifeServlet",如图2-4 所示。单击“Next”按钮后,选中“init"、 “destroy”等method,如图2-5所示。
如果servlet-api.jar未加入ClassPath (或者Java Build Path),便会出现如图2-6所示的错误。我们只需把鼠标指针移入代码错误处,如把鼠标指针移入“HttpServlet” 中,稍停片刻,便会弹出一个错误信息提示框,如图2-7所示,单击“Fix project setup…"超链接。
在弹出的"Project Setup Fixes” 对话框中,把E:tomcat8\lib 中的servlet-apijar 加入ServletDemo工程的Build Path,如图2-8所示。
LifsServlet.java 主要代码如下:
package com.mialab.servlet_demo; import java.io.IOException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class LifeServlet */ @WebServlet(asyncSupported = true, urlPatterns = { "/LifeServlet" }) public class LifeServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public LifeServlet() { super(); // TODO Auto-generated constructor stub System.out.println(this.getClass().getName() + "的构造方法被调用"); } /** * @see Servlet#init(ServletConfig) */ public void init(ServletConfig config) throws ServletException { // TODO Auto-generated method stub System.out.println(this.getClass().getName() + "的init()方法被调用"); } /** * @see Servlet#destroy() */ public void destroy() { // TODO Auto-generated method stub System.out.println(this.getClass().getName() + "的destroy()方法被调用"); } /** * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response) */ //protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //} /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub response.getWriter().append("Served at: ").append(request.getContextPath()); System.out.println(this.getClass().getName() + "的doGet()方法被调用"); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }
3. 部署并测试
在Eclipse中部署并运行LifeServlet.启动Eclipse中配置好的Tomcat,访问LifeServlet的URL为http://localhost:8080/ServletDemo/LifeServlet。
(可参见第1章: Eclipse 自动部署项目到 Tomcat 的 webapps 目录。也可以右击 LifeServlet.java ,在弹出的快捷菜单中选择“Run As→Run on Server”选项。)
Eclipse中的控制台将输出:
com.mialab.servlet_demo.LifeServlet的构造方法被调用 com.mialab.servlet_demo.LifeServlet的init()方法被调用 com.mialab.servlet_demo.LifeServlet的doGet()方法被调用
打开命令行窗口,在E:\tomcat8\bin路径下输入shutdown命令,关闭Tomcat服务器,如图2-9所示。
Eclipse中的控制台将输出:
2.3 Servlet API编程常用接口和类
2.3.1 GenericServlet 类
GenericServlet 类的部分源码如下:
package javax.servlet; ... public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { ... //用 transient 关键字标记的成员变量不参与序列化过程 private transient ServletConfig config; @Override public ServletConfig getServletConfig() { return config; } @Override public ServletContext getServletContext() { return getServletConfig().getServletContext(); } @Override public String getInitParameter (String name) { return getServletConfig().getInitParameter(name) ; } @Override public void init (ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { // NOOP by default } ... }
GenericServlet类是一个抽象类, 实现了Servlet 和ServletConfig接口,其作用如下。
(1)将init方法中的ServletConfig 赋给一个类中成员(ServletConfig config),以便可以通过调用get ServletConfig来获取。(关于transient关键字的说明: Java 的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient 型变量的值不包括在序列化的表示中,然而,非transient型的变量是被包括进去的。)
(2)为Servlet接口中的所有方法提供默认的实现。
(3)可以通过覆盖没有参数的init 方法来编写初始化代码,ServletConfig 则仍然由GenericServlet实例保存。
(4)开发者可以在不用获得ServletConfig 对象的情况下直接调用ServletConfig 的方法,如上述代码中的getServletContext()方法。
【示例】使用 GenericServlet 类
关于使用GenericServlet类的示例,可参见MyGenericServlet.java的代码(源码包资源第2章ServletDemo工程中的src/com.mialab.servlet_demo包),访问MyGenericServlet的URL是 http://ocalhost:8080/ServletDemo/generic。
2.3.2 HttpServlet类
HttpServlet类扩展了GenericServlet 类,其部分代码如下:
package javax .servlet.http; ... public abstract class HttpServlet extends GenericServlet { ... private static final String METHOD DELETE = "DELETE"; private static final String METHOD HEAD = "HEAD"; private static final String METHOD GET= "GET"; private static final String METHOD OPTIONS = "OPTIONS"; private static final String METHOD POST = "POST"; private static final String METHOD PUT = "PUT"; private static final String METHOD TRACE = "TRACE"; protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ ... } protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ... } //添加新的Service方法 protected void service (HttpServletRequestreq, HttpServletResponseresp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { ... doGet (req, resp); } else if (method.equals(METHOD_HEAD)) { ... doHead (req, resp); } else if (method.equals(METHOD_POST)) { doPost (req, resp); }else {... } } //覆盖的Service方法 @Override public void service (ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request F (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException ("non-HTTP request or response") ; } service request, response); } }
HttpServlet覆盖了GenericServlet 中的Service 方法,并添加了一个新Service 方法。
新Service方法接收的参数是HttpServletRequest 和HttpServletResponse,而不是Servlet-Request和ServletResponse。
原始的Service 方法将Servlet 容器的request 和response 对象分别转换成HttpServlet-Request和HtpServletResponse,并调用新的Service方法。
HttpServlet中的Service 方法会检验用来发送请求的HTTP方法(通过调用request.getMethod来实现)并调用以下方法之一 : doGet、 doPost、doHead、doPut、doTrace、doOptions和doDelete。在这7种方法中,doGet 和doPost是最常用的。所以,不再需要覆盖Service方法了,只须覆盖doGet或者doPost即可。
2.3.3 ServletConfig 接口
Tomcat初始化一个Servlet 时,会将该Servlet的配置信息封装到一个ServletConfig 对象中,通过调用init(ServletConfig config)方法将ServletConfig对象传递给Servlet。
ServletConfig接口的常用方法如表2-2所示。
表 2-2 ServletConfig 接口的常用方法
方法说明 | 功能描述 |
ServletContext getServletContext() | 获取ServletContext对象 |
String getServletName() | 返回当前Servlet的名称 |
String getInitParameter(String name) | 根据初始化参数名称返回对应的初始化参数值 |
Enumeration getInitParameterNames() | 返回一个Enumeration对象,其中包含了所有的初始化参数名 |
【示例】 ServletConfig 接口的使用。
ServletConfigDemoServlet.java主要代码如下:
package com.mialab.servlet_demo; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServletConfigDemoServlet extends HttpServlet { private ServletConfig servletConfig; @Override public void init(ServletConfig config) throws ServletException { this.servletConfig = config; System.out.println("-----------" + servletConfig + "-----------"); } @Override public ServletConfig getServletConfig() { return servletConfig; } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 使用ServletConfig对象获取初始化参数 ServletConfig servletConfig = getServletConfig(); System.out.println("-----------" + servletConfig + "-----------"); String poet = servletConfig.getInitParameter("poet"); String poem = servletConfig.getInitParameter("poem"); // 设置响应到客户端的文本类型为HTML response.setContentType("text/html;charset=UTF-8"); // 获取输出流 PrintWriter out = response.getWriter(); out.print("<p>获取ServletConfigDenoServlet的初始化参数:"); out.println("</p><p>poet参数的值:" + poet); out.println("</p><p>poem参数的值:" + poem + "</p>"); out.append("Served at:").append(request.getContextPath()); } }
Web.xml中关于ServletConfigDemoServlet 的配置如下:
<servlet> <servlet-name>myServletConfig</servlet-name> <servlet-class>com.mialab.servlet_demo.ServletConfigDemoServlet</servlet-class> <init-param> <param-name>poet</param-name> <param-value>纳兰容若</param-value> </init-param> <init-param> <param-name>poem</param-name> <param-value>我是人间惆怅客</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>myServletConfig</servlet-name> <url-pattern>/myServletConfig</url-pattern> </servlet-mapping>
访问 ServletConfigDemoServlet 的 URL:
http://localhost:8080/ServletDemo/myServletConfig
输出结果:
获取ServletConfigDemoServlet的初始化参数: poet参数的值:纳兰容若 poem参数的值:我是人间惆怅客 Served at: /ServletDemo
最后报错不知道为什么,看到的大佬帮解答下 (_)/
2.3.4 HttpServletRequest 接口
HttpServletRequest接口继承自ServletRequest接口,专门用来封装HTTP请求消息。由于HTTP请求消息分为请求行、请求消息头和请求消息体3部分,故而在HttpServletRequest接口中定义了获取请求行、请求消息头和请求消息体的相关方法,以及存取请求域属性的方法。
1. 获取请求行信息
HTTP_请求报文的请求行由请求方法、请求URL和请求协议及版本组成。HttpServletRequest接口对请求行各部分信息的获取方法如表2-3所示。
表 2 - 3 获取请求的相关方法
方法说明 | 功能描述 |
String getMethod() | 用于获取 HTTP 请求消息中的请求方式(如GET、POST等) |
String getRequestURI() | 用于获取请求行中资源名称部分,即位于URL的主机和端口之后、参数部分之前的部分 |
String getQueryString() | 用于获取请求行中的参数部分,也就是资源路径后面(? )以后的所有内容。其只对GET有效 |
String getProtocol() | 获取请求行中的协议名和版本 |
String getContextPath() | 获取请求URL中属于Web应用程序的路径。这个路径以“/"开头,表示整个Web站点的根目录,路径结尾不含 “/” |
String getServletPath() | 获取Servlet所映射的路径 |
String getRemoteAddr() | 用于获取请求客户端的IP地址 |
String getRemoteHost() | 用于获取请求客户端的完整主机名 |
String getScheme() | 用于获取请求的协议名,如 HTTP 等 |
StringBuffer getRequestURL() | 获取客户端发出请求时的完整URL,包括协议、服务器名、端口号、资源路径等信息,但不包括后面的查询参数部分 |
【示例】使用HttpServletRequest接口获取请求行信息。
关于使用 HttpServletRequest
接口获取请求行信息的示例,可参见RequestLineServet.java
的代码(源码包见本书资源第2章 ServletDemo
工程中的 src/com.mialab.servlet_demo
包),访问 RequestLineServlet
的 URL
是http://localhost:8080/ServletDemo/RequestLineServlet
2. 获取请求消息头
当请求Servlet时,需要通过请求头向服务器传递附加信息。HTTP常见的请求头如表2-4 所示。
表2-4 HTTP 常见的请求头
请求头名称 | 说明 |
Host | 初始 URL 中的主机和端口 |
Connection | 表示是否需要持久连接 |
Accpet | 浏览器可以接收的 MIME 连接 |
Accpet-Charset | 浏览器发给服务器,声明浏览器支持的编码类型 |
Content-Length | Content-Length用于描述HTTP消息实体的传输长度。在HTTP协议中,消息实体长度和消息实体的传输长度是有区别的,例如,在GZIP压缩下,消息实体长度是压缩前的长度,消息实体的传输长度是GZIP压缩后的长度 |
Cache-Control | 指定请求和响应遵循的缓存机制 |
User-Agent | User Agent的中文名为用户代理,简称UA,它是一一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU类型、浏览器类型及版本、浏览器渲染引擎、浏览器语言、浏览器插件等 |
Cache-Type | 表示请求内容的MIME类型 |
Cookie | 表示客户端的 Cookie 信息,即告诉服务器,上次访问时,服务器写了哪些 cookie 到客户端 |
HttpServletRequest接口中定义的用于获取HTTP请求头字段的方法,如表2-5所示。
表 2- 5 获取请求消息头的方法
请求头名称 | 说明 |
String getHeader(String name) | 用于获取一个指定头字段的值 |
Enumeration getHeaderNames() | 用于获取一个包含所有请求头字段的 Enumeration 对象 |
Enumeration getHeaders(String name) | 返回一个Enumeration 集合对象,该集合对象由请求消息中出现的某个指定名称的所有头字段值组成 |
int getIntHeader(String name) | 用于获取指定名称的头字段,并将其值转换为int 类型 |
Cookie[] getCookies() | 返回一个数组包含客户端请求的所有 Cookie 对象,如果没有发送任何信息,则这个方法返回 null |
String getContentType() | 用于获取 Content-Type 头字段的值 |
int getContentLength() | 用于获取 Content-Length 头字段的值 |
String getCharacterEncoding() | 用于返回请求消息的实体部分的字符集编码,通常是从 Content-Type 头字段中提取 |
【示例】使用HttpServletRequest接口获取请求头信息。
使用HttpServletRequest接口获取请求头信息的示例,可参见RequestHeadInfoServlet.java的代码(源码第2章ServletDemo 工程src.com.mialab.servlet demo 包),访问 RequestLineServlet 的 URL 是 http://locahost:8080/ServletDemo/RequestHeadInfoServlet。
3. 获取请求参数
在实际开发中,经常需要获取用户提交的表单数据。为了方便获取表单中的请求参数,在HttpServletRequest接口的父类ServletRequest 接口中定义了一系列获取请求参数的方法,如表2-6所示。
表 2 - 6 获取请求参数的方法
方法说明 | 功能描述 |
String getParameter(String name) | 用于获取某个指定名称的参数值 |
Enumeration getParameterNames() | 返回一个包含请求消息中所有参数名的Enumeration对象 |
String[] getParameterValues(String name) | 根返回由name指定的用户请求参数对应的一组值 |
Map getParameterMap() | 用于将请求消息中的所有参数名和值装进一个 Map对象中返回 |
【示例】使用HttpServletRequest接口获取请求参数。
关于使用 HttpServletRequest 接口获取请求参数的示例,可参见 RequestParaServlet.java 和person_ info.html( 源码包见第2章的ServletDemo工程),先访问person_info.html,填写个人信息,再提交给RequestParaServlet处理。
2.3.5 HttpServletResponse接口
HttpServletResponse接口继承自ServletResponse 接口,专门用来封装HTTP响应消息。由于HTTP响应消息分为状态行、响应消息头、消息体三部分,于是在HttpServletResponse接口中也相应定义了向客户端发送响应状态码、响应消息头、响应消息体的方法。
1. 响应状态码
HTTP协议响应报文的响应行,由报文协议和版本,以及状态码和状态描述构成。
当Servlet向客户端回送响应消息时,需要在响应消息中设置状态码。常见的响应状态码如下: 200表示请求成功; 500 表示服务器内部错误; 404 表示请求的资源(网页等)不存在;302表示资源(网页等)暂时转移到其他URL等。
HttpServletResponse按口提供的设置状态码开生成啊应状态行的万法有以下儿种。
(1) setStatus(int status)方法: setStatus(int status)方法用于设置HTTP响应消息的状态码,并生成响应状态行。正常情况下,Web服务器会默认产生一个状态码为200的状态行。
(2) sendError(int sc)方法和sendError(int sc, String msg)方法:第1个方法只是发送错误信息的状态码;而第2个方法除了发送状态码外,还可以增加一条用于提示说明的文本信息,该文本信息将出现在发送给客户端的正文内容中。
要说明的是,在实际开发中,一般不需要人为地修改设置状态码,容器会根据程序的运行状况自动响应发送响应的状态码。
2. 响应消息头
HttpServletResponse接口中定义了-一些设置HTTP响应头字段的方法,如表2-8所示。
表 2 - 8 设置响应头字段的方法
方法 | 功能描述 |
void setContentType(String type) | 设置Servlet输出内容的MIME类型 |
void setContentI ength(int len) | 设置响应消息的实体内容的大小,单位为字节 |
void setHeader(String name, String value) | 设置HTTP协议的响应头字段。name指定响应头字段的名称, value 指定响应头字段的值 |
void setCharacterEncoding(String charset) | 设置HTTP协议的响应头字段。name 指定响应头字段的名称, value 指定响应头字段的值 |
void addCookie(Cookie cookie) | 为Set-Cookie消息头增加-一个值,Set-Cookie 消息头表示应该记录下来的Cookie,即把Cookie发送给客户端 |
void setLocale(Locale loc) | 用于设置响应消息的本地化信息。对HTTP来说,就是设置Content-Language响应头字段和Content-Type头字段中的字符集编码部分 |
【示例】响应消息头
关于响应消息头示例,可参见ResponseHeadServlet.java的代码(源码见第2章ServletDemo工程的src/com.mialab.servlet _demo包),访问ResponseHeadServlet的URL是http://ocahost:.8080/ServletDemo/ResponseHeadServlet
3. 响应消息体
HttpServletResponse接口提供了两个获取不同类型输出流对象的方法,如表2-9所示。
表 2-9 HttpServletResponse 接口获取不同类型输出对象的方法
方法 | 功能描述 |
ServletOutputStream getOutputStream() | 返回字节输出流对象 ServletOutputStream |
PrintWriter getWriter() | 返回字符输出流对象 |
【示例】响应消息体。
响应消息体示例,可参见ResponsePicServlet.java的代码(第2章ServletDemo 工程的src/com.mialab.servlet_demo包),访问 ResponsePicServlet 的 URL 是 http://localhost:8080/ServletDemo/ResponsePicServlet
没跑出来
2.3.6 ServletContext 接口
Servlet 容器在启动一个 Web 应用时,会为该应用创建一个唯一的 ServletContext 对象供该应用中的所有Servlet 对象共享。Servlet 对象可以通过 ServletContext 对象来访问容器中的各种资源。获得 ServletContext 对象可以使用以下两种方式。
① 通过 ServletConfig 接口的 getServletContext() 方法获得 ServletContext 对象。
② 通过GenericServlet 抽象类的 getServletContext() 方法获得 ServletContext 对象,实质上该方法也调用了ServletConfig 的 getServletContext() 方法。
该方法也调用了 ServletConfig 的 getServletContext() 方法。
1. 获取 Web 应用的初始化参数
ServletContext 接口中定义了获取 Web 应用范围的初始化参数的方法。
(1)方法声明: public String getInitParameter(String name)
。
作用:放回 Web 应用范围指定的初始化值。在 Web.xml 中使用 元素表示应用范围内的初始化参数。
(2)方法声明: public Enumeration getInitParaNames(String name)。
作用:返回 Web 应用范围内指定的初始化参数值。在 Web.xml 中使用 元素表示应用范围内的初始化参数。
作用:返回一个包含所有初始化参数名称的 Enumeration 对象。
【示例】使用 ServletConext 接口获取 Web 应用的初始化参数。
获取Web应用初始化参数的示例,可参见GetWebInitParamServletjava 的代码
GetWebInitParamServlet.java中最重要的doGet方法代码如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub // 设置响应到客户端的文本类型为HTML response.setContentType("text/html;charset=UTF-8"); // 得到ServletContext对象 ServletContext context = this.getServletContext(); // 得到包含所有初始化参数名的Enumeration对象 Enumeration<String> paramNames = context.getInitParameterNames(); // 获取输出流 PrintWriter out = response.getWriter(); // 遍历所有的初始化参数名,得到相应的参数值,打印到控制台 out.print("<h2>当前Web应用的所有初始化参数:</h2>"); // 遍历所有的初始化参数名,得到相应的参数值并打印 while (paramNames.hasMoreElements()) { String name = paramNames.nextElement(); String value = context.getInitParameter(name); out.println(name + ":" + value); out.println("<br>"); } out.close(); }
Web应用的初始化参数在Web.xml中,主要代码如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <display-name>ServletDemo</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <context-param> <param-name>username</param-name> <param-value>admin888</param-value> </context-param> <context-param> <param-name>password</param-name> <param-value>123456</param-value> </context-param> <context-param> <param-name>driverClassName</param-name> <param-value>org.postgresql.Driver</param-value> </context-param> <context-param> <param-name>url</param-name> <param-value>jdbc:mysql://127.0.0.1:3306/test</param-value> </context-param> <servlet> <servlet-name>myServletConfig</servlet-name> <servlet-class>com.mialab.servlet_demo.ServletConfigDemoServlet</servlet-class> <init-param> <param-name>poet</param-name> <param-value>纳兰容若</param-value> </init-param> <init-param> <param-name>poem</param-name> <param-value>我是人间惆怅客</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>myServletConfig</servlet-name> <url-pattern>/myServletConfig</url-pattern> </servlet-mapping> <servlet> <servlet-name>ErrorHandler</servlet-name> <servlet-class>com.mialab.servlet_demo.ErrorHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ErrorHandler</servlet-name> <url-pattern>/ErrorHandler</url-pattern> </servlet-mapping> <error-page> <error-code>404</error-code> <location>/ErrorHandler</location> </error-page> <error-page> <exception-type>java.lang.Throwable</exception-type> <location>/ErrorHandler</location> </error-page> </web-app>
访问GetWebInitParamServlet的URL是http://localhost:8080/ServletDemo/GetWebInitParamServlet。
此处运行失败目前不知道哪里出现错误
2.实现多个Servlet对象共享数据
ServletContext对象的域属性可以被Web应用中的所有Servlet 访问。在ServletContext 接口中定义了存取ServletContext域属性的4个方法,如表2-10所示。
表2-10存取ServletContext域属性的4个方法
方法 | 功能描述 |
Object getAttribute(String name) | 根据参数指定的属性名返回一个与之匹配的域属性值 |
Enumeration gettributeNames() | 返回一个Enumeration 对象,该对象包含了所有存放在ServletContext中的域属性名 |
void setAttribute(String name, Object object) | 设置ServletContext的域属性 |
void removeAttribute(String name) | 根据参数指定的域属性名,从ServletContext中删除匹配的域属性 |
【示例】实现多个Servlet对象共享数据。
在PutContextDataServlet.java的doGet方法中写入以下代码:
ServletContext context = this.getServletContext(); // 通过setAttribute()方法设置属性值 context.setAttribute("contextData", "Here is contexData");
此示例代码可详见ServletDemo 工程的PutContextDataServlet.java 和GetContextDataServlet.java.访问http://localhost:8080/ServletDemo/GetContextDataServlet 的输出结果是 Here is contexData 。
3.读取Web应用下的资源文件
ServletContext接口中定义了一些读取Web资源的方法,如表2-11所示。
表2-11 ServletContext 接口访问Web资源的方法
方法 | 功能描述 |
String getRealPath(String path) | 返回资源文件在服务器文件系统上的真实路径(文件的绝对路径)。参数 path 表示资源文件的虚拟路径,它应该以正斜线“/“开始,“/”表示当前Web应用的根目录 |
Set getResourcePaths(String path) | 返回一个Set 集合,集合中包含资源目录中子目录和文件的路径名称。参数path必须以正斜线“/”开始 |
URL getResource(String path) | 返回映射到某个资源文件的URL对象。参数path必须以正斜线"/" 开始,"/"表示当前Web应用的根目录 |
String getMimeType(String fle) | 返回参数指定的文件的MIME类型 |
【示例】使用ServletContext接口读取Web资源。 |
GetResourceServlet.java中doGet方法的代码如下:
PrintWriter out = response.getWriter(); ServletContext context = this.getServletContext(); // 获取文件绝对路径 String path = context.getRealPath("/WEB-INF/classes/data.properties"); FileInputStream in = new FileInputStream(path); Properties pros = new Properties(); pros.load(in); out.println("username = " + pros.getProperty("username") + "<br>"); out.println("password = " + pros.getProperty("password") + "<br>"); out.println("driverClassName = " + pros.getProperty("driverClassName") + "<br>"); out.println("url = " + pros.getProperty("url") + "<br>");
ServletDemo工程src文件夹下data.propertis文件的内容如下。
username=root password=123456 driverClassName=org.postgresql.Driver url=jdbc:mysql://127.0.0.1:3306/test
先通过访问 http://localhost:8080/ServletDemo/GetResourceServlet 设置共享数据,再通过调用GetContextDataServlet 来得到共享数据。
2.4 Servlet 处理表单数据
表单在网页中主要负责数据采集功能。一个表单有以下3个基本组成部分。
(1)表单标签:包含了处理表单数据所用CGI程序(这里是用Servlet来做处理)的URL以及数据提交到服务器的方法。
(2)表单域:包含了文本框、密码框、隐藏域、多行文本框、复选框、单选框、下拉列表框和文件上传框等。
(3)表单按钮:包括提交按钮、复位按钮和一般按钮;用于将数据传送到服务器上的CGI脚本(这里是Servlet)或者取消输入,还可以用表单按钮来控制其他定义了处理脚本的处理工作。
表单数据是指通过表单让用户填写内容,然后提交到服务器上;这些数据被称之为表单数据。Servlet处理表单数据可以使用以下方法。
①getParameter(): 可以调用request.getParameter() 方法来获取表单参数的值。
②getParameterValues0: 如果参数出现一次以上,则调用该方法,并返回多个值,如复选框。
③getParameterNames0:如果要得到当前请求中的所有参数的完整列表,则调用该方法。
【示例】 Servlet处理表单数据。
Servlet处理表单数据的示例代码,可参见FormServlet.java 和form.html 先访问form.html,填写表单,再提交给FormServlet 处理。
package com.mialab.servlet_demo; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/FormServlet") public class FormServlet extends HttpServlet { private static final long serialVersionUID = 1L; public FormServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应到客户端MIME类型和字符编码方式 response.setContentType("text/html;charset=UTF-8"); // 设置request对象的解码方式 request.setCharacterEncoding("utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String sex = request.getParameter("sex"); String home = request.getParameter("home"); String info = request.getParameter("info"); // 获取输出流 PrintWriter out = response.getWriter(); out.println("<p>用户名:" + username + "</p>"); out.println("<p>密码:" + password + "</p>"); out.println("<p>性别:" + sex + "</p>"); out.println("<p>家乡:" + home + "</p>"); out.println("<p>爱好:"); // 获取参数名为“hobby”的值 String[] hobbys = request.getParameterValues("hobby"); for (int i = 0; i < hobbys.length; i++) { if (i < hobbys.length - 1) out.println(hobbys[i] + ","); else out.println(hobbys[i] + ""); } out.println("</p>"); out.println("<p>自我介绍:" + info + "</p>"); out.close(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Servlet处理表单数据</title> </head> <body> <h2>个人信息</h2> <hr> <form action="/ServletDemo/FormServlet" method="POST"> <p> 用户名:<input type="text" name="username"> </p> <p> 密 码 :<input type="password" name="password"> </p> <p> 性 别 :<input type="radio" name="sex" value="boy" checked>男<input type="radio" name="sex" value="girl">女 </p> <p> 家 乡 :<select name="home"><option value="suzhou">苏州</option> <option value="shanghai">上海</option> <option value="nanjing">南京</option> </select> </p> <p> 爱 好: <input type="checkbox" name="hobby" value="swim">游泳 <input type="checkbox" name="hobby" value="go">围棋 <input type="checkbox" name="hobby" value="music">音乐 </p> <p> 自我介绍:<br> <textarea name="info" rows="5" cols="26"></textarea> </p> <p> <input type="submit" value="提交"> </p> </form> </body> </body> </html>
2.5 Servlet 重定向和请求转发
请求转发只是把请求转发给服务器(通常是同一个Web应用中)的另一个组件( Servlet或JSP等);重定向则只是告诉客户(浏览器)去访问另一个URL (可能是同一个Web站点甚至其他站点)。请求转发发生在服务器端,由服务器(如Servlet)控制;重定向发生在客户端,由客户(通常是浏览器)控制。
请求转发过程在同一个请求中完成,只会返回一个响应。重定向过程则发生在两个不同的请求中,会返回两个不同响应。请求转发使用RequestDispatcher对象的forward0或include()方法。重定向则使HttpServletResponse对象的sendRedirect()方法。
请求转发和重定向方法都必须在响应提交(刷新响应正文输出到流中)之前执行,否则会抛出IllegalStateException异常。在转发或重定向之前,响应缓冲区中未提交的数据会被自动清除。
2.5.1 重定向
重定向后则无法在服务器端获取第一次请求对象时保存的信息。例如,在Servlet中将用户名保存到当前request对象中,并重定向到一个新的URL,然后在新URL指向的地址中(假设是某个Servlet) 就无法获取原先保存在第一一个请求中的信息。很明显,用户名是保存在第一次请求的对象中的,但并没有保存在本次(第二次)请求的对象中。
重定向后,浏览器地址栏URL变为新的URL (因为浏览器确实给新的URL发送了一个新的请求)。
【示例】 Servlet 网页重定向。
RedirectServlet.java的主要代码如下:
@WebServlet("/RedirectServlet") public class RedirectServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应到客户端的文本类型为HTML response.setContentType("text/html;charset=UTF-8"); // 输出当前时间 response.getWriter().println(new java.util.Date()); // 进行重定向 response.sendRedirect(request.getContextPath() + "/AnotherServlet"); } }
AnotherServlet.java的主要代码如下:
@WebServlet("/AnotherServlet") public class AnotherServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应到客户端的文本类型为HTML response.setContentType("text/html;charset=UTF-8"); // 获取输出流 PrintWriter out = response.getWriter(); // 输出响应结果 out.println("<p>重定向页面</p>"); out.close(); } }
访问RedirectServlet的URL是http://localhost:8080/ServletDemo/RedirectServlet 输出结果如图2-11所示。
sendRedirect()方法是HttpServletResponse 对象的方法,即响应对象的方法。既然调用了响应对象的方法,就表示本次请求过程已经结束了,服务器即将向客户端返回本次请求的响应。事实上,服务器确实返回了一个状态码为“302”,首部“Location”值为新的URL的响应。此后,浏览器会根据"Location”首部指定的URL,重新发起一次新的请求,转向这个目标页面,所以重定向实际。上发生在两个不同的请求中。
2.5.2 请求转发
@WebServlet("/OtherServlet") public class OtherServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应到客户端的文本类型为HTML response.setContentType("text/html;charset=UTF-8"); // 从request对象中获取bookname属性值 String bookname = (String) request.getAttribute("bookname"); // 获取输出流 PrintWriter out = response.getWriter(); // 输出响应结果 out.println("<p>请求转发的结果页面</p>"); out.println("读取的request对象的bookname属性值为:" + bookname); } }
访问ForwardServlet 的URL是http://locahost:8080/Servletvemo/ForwardServlet, 输出结果如图2-12所示。
图2-12 请求转发示例
RequestDispatcher对象是通过调用HttpServletRequest 对象的getRequestDispatcher()方法得到的,所以forward()或include()本质上属于请求对象的方法,所以请求转发始终发生在一个请求当中。
2.5.3 Servlet 中请求转发时 forwordO 和 include() 的区别
1.定义
forward():表示在服务器端从一个Servlet 中将请求转发到另一个资源(Servlet、 JSP 或HTML等),本意是让第一个组件对请求做出预处理(或者什么都不做),而让另一组件处理并返回响应。include():表示在响应中包含另一个资源(Servlet、 JSP 或HTML等)的响应内容,最终被包含的页面产生的任何响应都将并入原来的response对象,然后一起输出到客户端。
2.关于状态码和响应头
forward():调用者和被调用者设置的状态码和响应头都不会被忽略。include():被调用者(如被包含的Servlet)不能改变响应消息的状态码和响应头,即会忽略被调用者设置的状态码和响应头。
3.谁负责发回响应
forward():表示转发,控制权也同时交给了另一个组件,所以最终由另一组件返回响应。include():表示包含,则控制权还在自己身上,所以最终还是由自己返回响应。
4.请求转发后的代码是否执行
forward():转发后还会返回主页面继续执行,但不可以继续输出响应信息。include():转发后还会返回主页面继续执行,仍然可以继续输出响应信息。
5.关于forward(),引用Java EE文档中的说明
(1)必须在响应被提交到客户端(刷新响应正文输出到流中)前调用forward (即在调用forward之前必须清空响应缓冲区),否则会抛出IlgalStateException异常。
(2)在forward之前,响应缓冲区中未提交的数据会被自动清除。所以容器将忽略原Servlet所有其他输出。
6.补充说明:关于Servlet中的输出缓冲区
Handler是一个消息处理类,主要用于异步消息的处理:当发出一条消息之后,首先进入.一个消息队列,发送消息的函数即刻返回,而另外一部分逐个在消息队列中将消息取出,然后对消息进行处理,也就是说,发送消息和接收消息不是同步处理的。
(1)在Servlet中使用ServletOutputStream 和PrintWriter输出响应正文时,数据首先被写入Servlet引擎提供的一个输出缓冲区中。直到满足以下条件之一时,Servlet 引擎才会把缓冲区中的内容真正发送到客户端。
①输出缓冲区被填满。
②Servlet 已经写入了所有的响应内容。
③Servlet 调用响应对象的flushBuffer()方法,强制将缓冲区内的响应正文数据发送到客户端。
④Servlet 调用ServletOutputStream 或PrintWriter 对象的flush0方法或close0方法。
(2)为了确保ServletOutputStream或PrintWriter 输出的所有数据都能被提交给客户端,建议在所有数据输出完毕后,调用ServletOutputStream 或PrintWriter的close0方法。
(3)使用输出缓冲区后,Servlet 引擎就可以将响应状态行、各响应头和响应正文严格按照HTTP消息的位置顺序进行调整后再输出到客户端。
(4)如果在提交响应到客户端时,输出缓冲区中已经装入了所有的响应内容,Servlet 引擎将计算响应正文部分的大小并自动设置Content-Length 头字段。
(5)缓冲区自动刷新(清出)功能。[注意是刷新(flush), 而不是清除(flushBuffer) ]
①如果设置为自动刷新,则在缓冲区满或者使用flush(方法显式清出时,都会向客户端输出信息。
②如果设置为不自动刷新,则必须明确使用flush0方法清出数据,否则如果缓冲区满了,将会产生IOException异常。
(6)使用缓冲区能够减少数据传输的次数,提高程序的运行效率。但也有可能产生响应延迟的问题,因为在缓冲区满或使用flush0显式清出之前,数据并不会真正发送到客户端。
2.6 Servlet 数据库访问
2.6.1 JDBC 基础
Java数据库连接( Java Database Connectivity, JDBC)是一种用于执行 SQL语句的Java API,可以为多种关系数据库提供统一-访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够更为便利地编写数据库应用程序。
2.6.2 创建测试数据
在MySQL中创建book数据库,并创建book数据表,表结构如下:
/* Navicat MySQL Data Transfer Source Server : mysql Source Server Version : 50519 Source Host : localhost:3306 Source Database : book Target Server Type : MYSQL Target Server Version : 50519 File Encoding : 65001 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for book -- ---------------------------- DROP TABLE IF EXISTS `book`; CREATE TABLE `book` ( `bookId` int(4) NOT NULL, `bookName` varchar(50) NOT NULL, PRIMARY KEY (`bookId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在book数据表中插入以下几条测试数据:
-- ---------------------------- -- Records of book -- ---------------------------- INSERT INTO `book` VALUES ('9801', 'Android应用开发实践教程'); INSERT INTO `book` VALUES ('9802', 'Web应用开发'); INSERT INTO `book` VALUES ('9803', 'iOS程序设计');
2.6.3 访问数据库
这里访问的是MySQL数据库,所以需要把mysql-connector-java-5.1.39.jar 文件复制至Web-INF的lib目录下。
【示例】 Servlet 数据库访问。
在ServletDemo 工程的com.mialab.servlet_ demo包下创建DataAccessServlet.java DataAccessServlet.java主要代码如下:
@WebServlet("/DataAccessServlet") public class DataAccessServlet extends HttpServlet { // JDBC 驱动名及数据库 URL static final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://localhost:3306/book"; // 数据库的用户名与密码,需要根据自己的设置 static final String USER = "root"; static final String PASS = "1"; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn = null; Statement stmt = null; // 设置响应内容类型 response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<h2>Servlet数据库访问</h2>"); try { // 注册 JDBC 驱动器 Class.forName("com.mysql.jdbc.Driver"); // 打开一个连接 conn = DriverManager.getConnection(DB_URL, USER, PASS); // 执行 SQL 查询 stmt = conn.createStatement(); String sql; sql = "select bookId,bookName from book"; ResultSet rs = stmt.executeQuery(sql); // 展开结果集数据库 while (rs.next()) { // 通过字段检索 int bookId = rs.getInt("bookId"); String bookName = rs.getString("bookName"); // 输出数据 out.println("bookId:" + bookId); out.println(",bookName:" + bookName); out.println("<br />"); } //out.println("</body></html>"); // 完成后关闭 rs.close(); stmt.close(); conn.close(); } catch (SQLException se) { // 处理 JDBC 错误 se.printStackTrace(); } catch (Exception e) { // 处理 Class.forName 错误 e.printStackTrace(); } finally { // 最后是用于关闭资源的块 } } }
访问DataAccesServlet的URL是http://localhost:8080/ServletDemo/DataAccessServlet,浏览器窗口输出结果如图2-13所示。
2.7 Servlet异常处理
当一个Servlet抛出一一个异常时,Web容器在使用了exception-type 元素的Web.xml中搜索与抛出异常类型相匹配的配置。可以在Web.xml中使用error-page 元素来指定对特定异常或HTTP状态码出相应的Servlet 调用。
假设有一个ErrorHandler 的Servlet 在任何已定义的异常或错误出现时被调用,以下将是在Web.xml中创建的项。
<!-- Servlet定义--> <servlet> <servlet-name>ErrorHandler</servlet-name> <servlet-class>ErrorHandler</servlet-class> </servlet> <!-- Servlet映射 --> <servlet-mapping> <servlet-name>ErrorHandler</servlet-name> <url-pattern>/ErrorHandler</url-pattern> </servlet-mapping> <!-- error-code相关的错误页面--> <error-page> <error-code>404</error-code> <location>/ErrorHandler</location> </error-page> <error-page> <error-code>403</error-code> <location>/ErrorHandler</location> </error-page> <!-- exception-type 相关的错误页面--> <error-page> <exception-type> javax.servlet.ServletException </exception-type > <location>/ErrorHandler</location> </error-page> <error-page> <exception-type>java.io.IOException</exception-type > <location>/ErrorHandler</location> </error-page>
关于上面的Web.xml中的异常处有以下几点需要说明。
①ErrorHandler 与其他的Servlet的定义方式一样,且在Web.xml中进行配置。
②如果有错误状态代码出现,不管为404 (Not Found,即未找到)或403 (Forbidden,即禁止),都会调用ErrorHandler.
③如果Web应用程序抛出ServletException或IOException, Web容器会调用ErrorHandler.
④可以定义不同的错误处理程序来处理不同类型的错误或异常。如果要对所有的异常有-一个通用的错误处理程序,那么应该定义下面的error-page,而不是为每个异常定义单独的error-page元素:
<error-page> <exception-type>java.lang>Throwable</exception-type> <location>ErrorHandler</location> </error-page>
以下是错误/异常处理的Servlet可以访问的请求属性列表,用来分析错误/异常的性质。
(1) javax.servlet.error.status_ code: 该属性给出状态码,状态码可被存储,并可在存储为java.lang.Integer数据类型后被分析。
(2) javax.servleterror.exception_ type: 该属性给出异常类型的信息,异常类型可被存储,并可在存储为java.lang.Class 数据类型后被分析。
(3) javax.serleterror.message: 该属性给出确切错误消息的信息,信息可被存储,并可在存储为java.lang.String 数据类型后被分析。
(4) javax.servlet.eror.request_ _uri; 该属性给出有关URL调用Servlet 的信息,信息可被存储,并可在存储为java.lang.String 数据类型后被分析。
(5) javax.servlet.error.exception: 该属性给出异常产生的信息,信息可被存储,并可在存储为java.lang.Throwable 数据类型后被分析。
(6) javax.servleterror.servlet name:该属性给出Servlet 的名称,名称可被存储,并可在存储为java.lang.String 数据类型后被分析。
【示例】 Servlet 异常处理。
在ServletDemo工程的com.mialab.servlet demo包下创建ErrorHandlerServlet.java 和ExceptionServlet.java。ErrorHandlerServlet.java 主要代码如下:
public class ErrorHandlerServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception"); Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); String servletName = (String) request.getAttribute("javax.servlet.error.servlet_name"); if (servletName == null) { servletName = "Unknown"; } String requestUri = (String) request.getAttribute("javax.servlet.error.request_uri"); if (requestUri == null) { requestUri = "Unknown"; } // 设置响应内容类型 response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String title = "Servlet处理 Error/Exception"; String docType = "<!DOCTYPE html>\n"; out.println( docType + "<html>\n" + "<head><title>" + title + "</title></head>\n" + "<body bgcolor=\"#f0f0f0\">\n"); out.println("<h3>Servlet异常/错误处理</h3>"); if (throwable == null && statusCode == null) { out.println("<h3>错误信息丢失</h2>"); out.println("请返回 <a href=\"" + response.encodeURL("http://localhost:8080/") + "\">主页</a>。"); } else if (statusCode != null) { out.println("错误代码 : " + statusCode + "<br><br>"); out.println("Servlet Name : " + servletName + "</br></br>"); out.println("异常类型 : " + throwable.getClass().getName() + "</br></br>"); out.println("请求 URI: " + requestUri + "<br><br>"); out.println("异常信息: " + throwable.getMessage()); } else { // out.println("<h3>错误信息</h3>"); // out.println("Servlet Name : " + servletName + "</br></br>"); // out.println("异常类型 : " + throwable.getClass().getName() + "</br></br>"); // out.println("请求 URI: " + requestUri + "<br><br>"); // out.println("异常信息: " + throwable.getMessage()); } out.println("</body>"); out.println("</html>"); } }
ExceptionServlet.java主要代码如下:
@WebServlet("/ExceptionServlet") public class ExceptionServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int x = 126/0; //int[] array = {2,4,6}; //System.out.println(array[3]); //throw new ArrayIndexOutOfBoundsException(); } }
在Web.xml中须添加如下设置代码:
<servlet> <servlet-name>ErrorHandler</servlet-name> <servlet-class>com.mialab.servlet_demo.ErrorHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ErrorHandler</servlet-name> <url-pattern>/ErrorHandler</url-pattern> </servlet-mapping> <error-page> <error-code>404</error-code> <location>/ErrorHandler</location> </error-page> <error-page> <exception-type>java.lang.Throwable</exception-type> <location>/ErrorHandler</location> </error-page>
访问ExceptionServlet的URL是http://localhost:8080/ServletDeno/ExceptionServlet浏览器输出结果如图2-14所示。但浏览器往往要做设置,默认设置很可能不认可开发者的定制。要说明的是,我们使用外部的火狐浏览器来做测试,需要进入火狐浏览器的“选项→隐私与安全”标签页,勾选“允许Firefox 向Mozilla发送错误报告”复选框,如图2-15所示。
2.8 异步 Servlet
在Servlet 3.0之前,Servlet 采用Thread-Per-Request的方式处理请求,即每一次HTTP请求都由某一个线程从头到尾负责处理。如果-一个请求需要进行I0操作,如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成,而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts 这样的高层框架也脱离不了这样的桎梏,因为它们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet3.1中又引入了非阻塞I0来进一步增强异步处理的性能。
在Servlet3.0中,可以从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,Request 和Response对象都可从中获取。AsyncContext 可以从当前线程传给其他的线程,在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以返回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另-一个线程处理的过程便构成了Servlet 3.0中的异步处理。
编写异步Servlet 的步骤如下。
(1)调用ServletRequest中的startAsync方法。startAsync方法返回- -个AsyncContext。
(2)调用AsyncContext的setTimeout(),传递容器等待任务完成的超时时间的毫秒数。
(3)调用asyncContext.start,传递- -个Runnable来执行-一个长时间运行的任务。
(4)调用Runnable的asyncContext.complete或asyncContext.dispatch来完成任务。
【示例】异步Servlet。
在ServletDemo 工程的com.mialab.servlet demo包下创建SimpleAsyncServlet.java。
SimpleAsyncServlet主要代码如下:
@WebServlet(name = "/SimpleAsyncServlet", urlPatterns = { "/simpleAsync" }, asyncSupported = true) public class SimpleAsyncServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final AsyncContext asyncContext = request.startAsync(); System.out.println(Thread.currentThread().getName()); request.setAttribute("boyName", "李寻欢"); asyncContext.setTimeout(6000); asyncContext.start(new Runnable() { @Override public void run() { try { int millis = ThreadLocalRandom.current().nextInt(3000); String currentThread = Thread.currentThread().getName(); System.out.println(currentThread + " sleep for " + millis + " milliseconds."); Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } request.setAttribute("girlName", "我不是潘金莲"); asyncContext.dispatch("/boygirl.jsp"); } }); } }
在ServletDemo工程的WebContent文件夹下创建boygirl.jsp。boygirl.jsp 的代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>访问Request域</title> </head> <body> <p>Hello</p><hr> <p>girlName:${girlName}</p> <p>boyName:${boyName}</p> </body> </html>
访问SimpleAsyncServlet的URL是 http://localhost:8080/ServletDemo/simpleAsync ,浏览器输出的结果如图2-16所示。
图2-16异步Servlet示例
2.9 本章小结
Servlet运行在服务器端,由Servlet 容器所管理。Servlet 容器也称 Servlet 引擎,是Web服务器或应用服务器的一部分。Java Servlet通常情况下与使用 CGI 实现的程序可以达到异曲同工的效果。与传统的CGI和许多其他类似CGI的技术相比,Java Servlet 具有更高的效率,更容易被使用,功能更强大,具有更好的可移植性,也更节省投资。
Servlet可以由javax.servlet 和 javax.servlt.http 包创建,它是 Java 企业版的标准组成部分,Java 企业版是支持大型开发项目的 Java 类库的扩展版本。Servlet 负责执行的主要任务如下。
(1)读取客户端(浏览器)发送的显式的数据。这包括网页上的HTML表单,也可以是来自applet 或自定义的HTTP客户端程序的表单。
(2)读取客户端(浏览器)发送的隐式的HTTP请求数据。这包括 Cookies、 媒体类型和浏览器能理解的压缩格式等。
(3)处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。
(4)发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML或XML)、二进制文件(GIF 图像)、Excel 等。
(5)发送隐式的HTTP响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(如HTML),设置Cookies和缓存参数,以及其他类似的任务。
习题2
1.如何用记事本写一个Servlet,并在Tomcat服务器上部署运行?
2.Servlet 的体系结构是怎样的?
3.Servlet 的生命周期是怎样的?试编程加以说明
4.Servlet 如何处理表单数据?试编程加以说明。
5.什么是Servlet的重定向和请求转发?试编程加以说明。
6.什么是Servlet的请求转发?试编程加以说明。
7.Servlet 是如何访问数据库并实现数据库记录的增、删、改、查的?试编程加以说明。
8.Servlet 异常处理是怎样的?试编程加以说明。
9.什么是异步Servlet?
10.Servlet 处理HTTP请求的流程是什么
11.Servlet 如何产生HTTP回应?
12.RequestDispatcher 接口的forward0方法与include0方法的区别是什么?
13.如何初始化Web应用程序?试编程加以说明。
14.如何用Context起始参数建立JDBC数据库连接,并进行数据库查询?试编程加以说明。
15.如何存取Servlet起始参数?试编程加以说明。