第 2 章 Servlet 编程

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 第 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.4 Tomcat5.x Servlet3.0 Tomcat7.x
Servlet2.4 Tomcat6.x Servlet3.1 Tomcat8.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.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 包),访问 RequestLineServletURL 是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>
                密 &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起始参数?试编程加以说明。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
3月前
|
安全 Java API
Java中的Servlet编程详解
Java中的Servlet编程详解
|
12月前
|
SQL 前端开发 Java
JSP网上订餐管理系统myeclipse开发sql数据库BS模式java编程servlet技术mvc框架
JSP 网上订餐管理系统是一套完善的web设计系统,对理解JSP java编程开发语言有帮助servlet技术mvc框架,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0,使用java语言开发,系统主要采用B/S模式开发。
66 0
|
安全 Java 编译器
【Java Web编程 八】深入理解Servlet常用对象
【Java Web编程 八】深入理解Servlet常用对象
119 1
|
存储 Java
【Java Web编程 十一】深入理解Servlet监听器
【Java Web编程 十一】深入理解Servlet监听器
101 0
【Java Web编程 十一】深入理解Servlet监听器
|
Java 数据安全/隐私保护
【Java Web编程 十】深入理解Servlet过滤器
【Java Web编程 十】深入理解Servlet过滤器
122 0
|
前端开发 Java 应用服务中间件
【Java Web编程 七】Servlet基本概念和生命周期
【Java Web编程 七】Servlet基本概念和生命周期
142 0
带你吃透Servlet核心编程下篇(完整图文教程)(下)
文章目录 1 Http协议 1.1 什么是 HTTP 协议 1.2 GET请求与POST请求 1.3 响应的HTTP协议格式 1.4 MIME数据类型 2 HttpServletRequest类 2.1 HttpServletRequest说明及常用方法 2.2 HttpServletRequest类演示 2.3 获取请求表单中的参数值(POST请求) 2.4 解决post请求中的中文乱码问题 3 请求转发 4 HttpServletResponse类 4.1 两个输出流 4.2 如何回传客户端数据 5 请求重定向 5.1 什么是请求重定向 5.2 请求重定向演示
带你吃透Servlet核心编程下篇(完整图文教程)(下)
|
XML 应用服务中间件 数据格式
带你吃透Servlet核心编程下篇(完整图文教程)(中)
文章目录 1 Http协议 1.1 什么是 HTTP 协议 1.2 GET请求与POST请求 1.3 响应的HTTP协议格式 1.4 MIME数据类型 2 HttpServletRequest类 2.1 HttpServletRequest说明及常用方法 2.2 HttpServletRequest类演示 2.3 获取请求表单中的参数值(POST请求) 2.4 解决post请求中的中文乱码问题 3 请求转发 4 HttpServletResponse类 4.1 两个输出流 4.2 如何回传客户端数据 5 请求重定向 5.1 什么是请求重定向 5.2 请求重定向演示
带你吃透Servlet核心编程下篇(完整图文教程)(中)
|
XML 前端开发 JavaScript
带你吃透Servlet核心编程下篇(完整图文教程)(上)
文章目录 1 Http协议 1.1 什么是 HTTP 协议 1.2 GET请求与POST请求 1.3 响应的HTTP协议格式 1.4 MIME数据类型 2 HttpServletRequest类 2.1 HttpServletRequest说明及常用方法 2.2 HttpServletRequest类演示 2.3 获取请求表单中的参数值(POST请求) 2.4 解决post请求中的中文乱码问题 3 请求转发 4 HttpServletResponse类 4.1 两个输出流 4.2 如何回传客户端数据 5 请求重定向 5.1 什么是请求重定向 5.2 请求重定向演示
带你吃透Servlet核心编程下篇(完整图文教程)(上)
带你吃透Servlet核心编程上篇(完整图文教程)(下)
文章目录 1 走进Servlet 1.1 Servlet简介 1.2 第一个Servlet程序 1.3 Servlet程序如何定位到url地址 1.4 Servlet的生命周期方法 1.5 Servlet请求的分发处理 1.6 通过继承HttpServlet类实现Servlet程序 2 Servlet体系 2.1 Servlet继承体系 2.2 ServletConfig类的使用
带你吃透Servlet核心编程上篇(完整图文教程)(下)