第 2 章 Servlet 编程

第 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)服务器将响应返回给客户端。

  1. Servlet 的存在就是要为客户端服务
    Servlet的任务是得到一个客户端的请求,再发回一个响应。请求可能很简单一“ 请给我一个欢迎页面。”,也可能很复杂一“ 为我的购物车结账。”这个请求携带着一些重要的数据,Servlet 代码必须知道怎么找到和使用这个请求。响应也携带着些一信息,浏览器需要这些信息来显示一个页面,Servlet 代码必须知道怎么发送这些信息。

  2. Servlet 3.0
    Servlet 3.0作为JavaEE 6规范体系中的一员,随着Java EE6规范起一发布。该版本在前版本(Servlet2.5)的基础上提供了若干新特性,以用于简化Web应用的开发和部署。

(1)异步处理支持:有了该特性,Servlet 线程不再需要一直阻塞, 直到业务处理完毕才能再输出响应,最后才能结束该Servlet线程。在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,自已在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。
(2)新增的注解支持:该版本新增了若干注解,用于简化Servlet、过滤器(Filter) 和监听器(Listener) 的声明,这使得Web.xml部署描述文件从该版本开始不再是必选的了。

  1. Servlet 与Tomcat版本支持
    以目前主流的Web服务器Tomcat (包含Servlet容器)为例,Tomcat对Servlet版本的支持关系如表2-1所示。

表2-1 Servlet与Tomcat版本支持

Servlet 版本号Tomcat 版本号Servlet版本号Tomcat版本号
Servlet2.4Tomcat5.xServlet3.0Tomcat7.x
Servlet2.4Tomcat6.xServlet3.1Tomcat8.x

2.2 Servlet 基础

2.2.1 用记事本写一个 Servlet

  1. 编写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(); 
    }
}
  1. 编译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字节码文件。

  1. 部署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应用,只不过是手工创建的。

  1. 访问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.LifeServletinit()方法被调用
com.mialab.servlet_demo.LifeServletdoGet()方法被调用

      打开命令行窗口,在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 包),访问 RequestLineServletURL 是http://localhost:8080/ServletDemo/RequestLineServlet

       2. 获取请求消息头

       当请求Servlet时,需要通过请求头向服务器传递附加信息。HTTP常见的请求头如表2-4 所示。

                                                                       表2-4 HTTP 常见的请求头

请求头名称说明
Host初始 URL 中的主机和端口
Connection表示是否需要持久连接
Accpet浏览器可以接收的 MIME 连接
Accpet-Charset浏览器发给服务器,声明浏览器支持的编码类型
Content-LengthContent-Length用于描述HTTP消息实体的传输长度。在HTTP协议中,消息实体长度和消息实体的传输长度是有区别的,例如,在GZIP压缩下,消息实体长度是压缩前的长度,消息实体的传输长度是GZIP压缩后的长度
Cache-Control指定请求和响应遵循的缓存机制
User-AgentUser 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>&nbsp;&nbsp;&nbsp;码 :<input type="password" name="password">
            </p>
            <p>&nbsp;&nbsp;&nbsp;别 :<input type="radio" name="sex" value="boy"
                                              checked><input type="radio" name="sex" value="girl"></p>
            <p>&nbsp;&nbsp;&nbsp;乡 :<select name="home"><option
                                                                   value="suzhou">苏州</option>
                <option value="shanghai">上海</option>
                <option value="nanjing">南京</option>
                </select>
            </p>
            <p>&nbsp;&nbsp;&nbsp;好: <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起始参数?试编程加以说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

真题OK撒

你的打赏将是我最大的创作

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值