Servlet 本章要点: ? Servlet的工作原理 ? Servlet的基本编程技能,包括请求处理、响应生成和参数配置等 ? Servlet的高级编程技能,包括会话管理、上下文和Servlet间协作 ? Servlet Filter的工作原理和编程方法 ? Servlet Listener的工作原理和编程方法 本章首先讲解Servlet的定义和工作原理,随后通过示例讲解Servlet编程基础包括请求处理、响应生成和参数配置等;在此基础上,对会话管理、Servlet上下文、Servlet间协作等高级编程技巧进行深入讲解;最后介绍Servlet编程中的两类高级功能组件Filter和Listener。 3.1 Web应用模型 Java EE企业应用最常见的场景就是处理Web请求并生成动态响应。因此Java EE学习之旅的第一站自然从Java EE的Web组件Servlet开始。不过在学习Servlet 编程之前,开发人员应该首先了解Web应用是如何工作的。 所谓Web应用,指的是可通过Web访问的应用程序,如门户网站等。区别于在计算机本地运行的桌面应用如Word、Excel等,Web应用由客户端和服务器两部分组成,二者通过HTTP协议进行交互,如图3-1所示。 图3-1 Web应用模型 HTTP是Web应用最常用的协议。最广泛使用的HTTP版本是1.1,它工作在请求响应模式下,一次请求处理流程包含如下四个步骤。 (1)客户端向服务器发送一个请求,请求头部包含请求的方法、URI、协议版本,以及包含请求修饰符、客户端信息和内容的类似MIME的消息结果。 (2)服务器接收到请求信息后建立与客户端的连接。 (3)服务器对客户端提交的请求信息进行处理,并最终返回一个响应,内容包括消息协议的版本、成功或失败编码加上包含服务器信息、实体元信息以及其他内容。 (4)服务器断开与客户端的连接。 如果客户端需要再次向服务器请求信息,则进入如上所示新一轮的处理流程。 对于HTTP 1.1协议有以下两个特性开发人员必须要牢记: (1)HTTP协议是无状态的。服务器并不会记录和保存客户端的任何信息。也就是说,同一用户在第二次访问同一服务器上的页面时,服务器的响应过程与第一次被访问时相同。至于服务器如何处理来自同一客户端的请求,将在3.7节进行深入讲解。 (2)HTTP是无连接的。服务器并不会保持与客户端的永久性连接。服务器只是在收到客户端的请求后才会与客户端建立起连接,一旦服务器生成响应并返回客户端,服务器就将断开与客户端的连接。如果客户端需要请求服务器上另外一个资源,则需要重新建立与服务器的连接。 3.2 Servlet基础 3.2.1 Servlet定义 Servlet是服务器端的Java应用程序,它用来扩展服务器的功能,可以生成动态的Web页面。Servlet与传统Java应用程序最大的不同在于:它不是从命令行启动的,而是由包含Java虚拟机的Web服务器进行加载。 Applet是运行于客户端浏览器的Java应用程序,Servlet与Applet相比较,有以下特点。 1.相似之处 (1)它们不是独立的应用程序,没有main方法。 (2)它们不是由用户调用,而是由另外一个应用程序(容器)调用。 (3)它们都有一个生命周期,包含init和destroy方法。 2.不同之处 (1)Applet运行在客户端,具有丰富的图形界面。 (2)Servlet运行在服务器端,没有图形界面。 造成这种差别的原因在于它们所肩负的使命不同。Applet目的是为了实现浏览器与客户的强大交互,因此需要丰富多样的图形交互界面;Servlet用于扩展服务器端的功能,实现复杂的业务逻辑,它不直接同客户交互,因此不需要图形界面。 Servlet 最大的用途是通过动态响应客户端请求来扩展服务器功能。 3.2.2 Servlet工作流程 Servlet运行在Web服务器上的Web容器里。Web容器负责管理Servlet。它装入并初始化Servlet,管理Servlet的多个实例,并充当请求调度器,将客户端的请求传递到Servlet,并将Servlet的响应返回给客户端。Web容器在 Servlet 的使用期限结束时终结该 Servlet。服务器关闭时,Web容器会从内存中卸载和除去 Servlet。 Servlet的基本工作流程如下: (1)客户端将请求发送到服务器。 (2)服务器上的Web容器实例化(装入)Servlet,并为Servlet进程创建线程。请注意,Servlet是在出现第一个请求时装入的,在服务器关闭之前不会卸载它。 注意:Servlet也可以配置为Web应用程序启动时自动装载。关于如何配置Servlet将在3.6节详细讲解。 (3)Web容器将请求信息发送到Servlet。 (4)Servlet 创建一个响应,并将其返回到Web容器。Servlet使用客户端请求中的信息以及服务器可以访问的其他信息资源(如资源文件和数据库等)来动态构造响应。 (5)Web容器将响应返回客户端。 (6)服务器关闭或Servlet空闲时间超过一定限度时,调用destroy方法退出。 从上面Servlet的工作基本流程可以看出,客户端与Servlet间没有直接的交互。无论是客户端对Servlet的请求还是Servlet对客户端的响应,都是通过Web容器来实现的,这就大大提高了Servlet组件的可移植性。 下面对Servlet的工作基本流程进行详细说明。 1.Servlet装入和初始化 第一次请求Servlet时,服务器将动态装入并实例化 Servlet。开发人员可以通过Web配置文件将Servlet配置成在Web服务器初始化时直接装入和实例化。Servlet 调用 init方法执行初始化。init方法只在 Servlet创建时被调用,所以,它常被用来作为一次性初始化的工作,如装入初始化参数或获取数据库连接。 init方法有两个版本:一个没有参数,一个以 ServletConfig 对象作为参数。 2.调用Servlet 每个Servlet都对应一个URL地址。Servlet 可以作为显式 URL 引用调用,或者嵌入在HTML中并从Web应用程序调用。 Servlet和其他资源文件(如JSP文件、静态HTML文本等)打包作为一个Web应用存放在Web服务器上。对于每个Web应用,都可以存在一个配置文件web.xml。关于Servlet的名称、对应的Java类文件、URL地址映射等信息都存放在配置文件web.xml中。当Web服务器接收到对URL地址的请求信息时,会根据配置文件中URL地址与Servlet之间的映射关系将请求转发到指定的Servlet来处理。 说明:自Java EE 6版本以来,Java EE规范推荐使用注解来配置Web组件,而不是使用配置文件web.xml。注解是内嵌在Java代码中的一种特殊标记,关于注解的使用本书后面的示例中会反复讲到。因此,在Java EE 6版本以上的 Web应用中,也允许没有配置文件web.xml存在。 3.处理请求 当Web容器接收到对 Servlet 的请求,Web容器会产生一个新的线程来调用Servlet的service方法。service方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),然后相应地调用Servlet组件的 doGet、doPost、doPut、doDelete等方法。如果Servlet 处理各种请求的方式相同,也可以尝试覆盖service方法。GET请求类型与POST请求类型的区别在于:如果以GET方式发送请求,所带参数附加在请求URL后直接传给服务器,并可从服务器端的QUERY_STRING这个环境变量中读取;如果以POST方式发送请求,则参数会被打包在数据包中传送给服务器。 4.多个请求的处理 Servlet由Web容器装入,一个Servlet同一时刻只有一个实例,并且它在 Servlet 的使用期间将一直保留。当同时有多个请求发送到同一个Servlet时,服务器将会为每个请求创建一个新的线程来处理客户端的请求。 如图3-2所示,有两个客户端浏览器同时请求同一个 Servlet服务,服务器会根据Servlet实例对象为每个请求创建一个处理线程。每个线程都可以访问Servlet装入时的初始化变量。每个线程处理它自己的请求。Web容器将不同的响应返回各自的客户端。 图3-2 Servlet对多个请求的处理 上述说明意味着Servlet的doGet方法和doPost方法必须注意共享数据和领域的同步访问问题,因为多个线程可能会同时尝试访问同一块数据或代码。如果想避免多线程的并发访问,可以设置Servlet实现SingleThreadModel接口,如下所示: public class YourServlet extends HttpServlet implements SingleThreadModel { ... } 注意:使用SingleThreadModel接口虽然避免了多请求条件下的线程同步问题,但是单线程模式将对应用的性能造成重大影响,因此在使用时要特别慎重。 5.退出 如果Web应用程序关闭或者Servlet已经空闲了很长时间,Web 容器会将Servlet实例从内存移除。移除之前Web 容器会调用Servlet的destroy方法。Servlet可以使用这个方法关闭数据库连接、中断后台线程、向磁盘写入Cookie列表及执行其他清理动作。 注意:当Web容器出现意外而被关闭,则不能够保证destroy方法被调用。 通过上面Servlet工作流程的基本描述,对于Web容器的职责,可以归纳为以下两点:一是管理Servlet组件的生命周期,包括Servlet组件的初始化、销毁等;二是作为客户端与Servlet之间的中介,负责封装客户端对Servlet的请求,并将请求映射到对应的Servlet,以及将Servlet产生的响应返回给客户端。 3.2.3 Servlet编程接口 Java EE标准定义了Java Servlet API,用于规范Web容器和Servlet 组件之间的标准接口。Java Servlet API是一组接口和类,主要由两个包组成:javax.servlet 包含了支持协议无关的 Servlet 的类和接口;javax.servlet.http 包括了对 HTTP 协议的特别支持的类和接口。如果希望详细了解Java Servlet API,可访问http://www.oracle.com/technetwork/java/ index-jsp-135475.html下载Java Servlet API的详细文档。 所有的Servlet都必须实现通用Servlet 接口或HttpServlet 接口。通用Servlet 接口类javax.servlet.GenericServlet定义了管理 Servlet 及它与客户端通信的方法;HttpServlet 接口类javax.servlet.http.HttpServlet是继承了通用Servlet 接口类的一个抽象子类。要编写在Web上使用的 HTTP协议下的Servlet,通常采用继承 HttpServlet 接口的形式。下面以HttpServlet 接口为中心,介绍与Servlet编程密切相关的几个接口,如图3-3所示。 图3-3 Servlet编程相关接口示意图 * HttpServletRequest代表发送到HttpServlet 的请求。这个接口封装了从客户端到服务器的通信。它可以包含关于客户端环境的信息和任何要从客户端发送到Servlet的数据。 * HttpServletResponse代表从HttpServlet返回客户端的响应。它通常是根据请求和 Servlet 访问的其他来源中的数据动态创建生成的响应,如HTML页面。 * ServletConfig代表Servlet的配置信息。Servlet在发布到服务器上的时候,在Web应用配置文件中对应一段配置信息。Servlet根据配置信息进行初始化。配置信息的好处在于在Servlet发布时可以通过配置信息灵活地调整Servlet而不需要重新改动、编译代码。 * ServletContext代表Servlet的运行环境信息。Servlet是运行在服务器上的程序。为了与服务器及服务器上运行的其他程序进行交互,有必要获得服务器的环境信息。 * ServletException代表Servlet运行过程中抛出的意外对象。 * HttpSession用来在无状态的HTTP协议下跨越多个请求页面来维持状态和识别用户。维护HttpSession的方法有Cookie或URL重写。 * RequestDispatcher:请求转发器,可以将客户端请求从一个Servlet转发到其他的服务器资源,如其他Servlet、静态HTML页面等。 Java EE服务器必须声明支持的Java Servlet API的版本级别。随着Java EE技术的不 断进步,Java Servlet API的版本也在不断更新,在Java EE 8标准规范中包含的Java Servlet API的版本为4.0。 3.3 第一个Servlet 在了解了Servlet的基础知识后,现在开始编写第一个Servlet组件。 编写响应HTTP请求的Servlet只需要两步: (1)创建一个扩展了javax.servlet.http.HttpServlet接口的类。javax.servlet.http.HttpServlet接口是javax.servlet.GenericServlet的扩展接口,它包含了分析HTTP 请求Header和将客户端信息打包到javax.servlet.http.HttpServletRequest类中的相关代码。 (2)重写Servlet组件的doGet或doPost方法实现对HTTP请求信息的动态响应。这些方法是Servlet实际完成工作的地方。HTTP 1.1支持七种请求方法: GET、POST、 HEAD、 OPTIONS、PUT、DELETE和TRACE。GET和POST是Web应用程序中最常用的两个方法。根据请求是通过GET还是POST发送,覆盖doGet、doPost方法之一或全部。doGet和doPost方法都有两个参数,分别为HttpServletRequest 接口和HttpServletResponse接口。HttpServletRequest提供访问有关客户端请求信息的方法,包括表单数据、请求Header等。HttpServletResponse除了提供用于指定HTTP应答状态(200、404等)、应答头部信息(Content-Type、Set-Cookie等)的方法之外,最重要的是它提供了一个用于向客户端发送数据的输出流对象。这个输出流对象可以是字节流或二进制数据流。对于Servlet开发来说,它的大部分工作是操作此输出流并返回给客户端。 提示:doGet和doPost这两个方法是由service方法调用的,有时可能需要直接覆盖service方法,例如Servlet要对GET和POST两种请求采用同样的处理方式。但不推荐那样做。 Servlet也可以重写init和destroy方法以实现Servlet定制化的初始化和析构。重写init和destroy方法的典型场景是在init方法中建立数据库连接并在destroy方法中断开它。 下面开始创建Servlet。Servlet作为一个Web组件,必须包含在某个Web应用程序中,因此,首先创建Web应用程序Chapter3。 注:本章中所有的示例都包含在此Web应用程序中。 打开NetBeans开发环境,单击“文件”菜单的“新建项目”选项,弹出如图3-4所示的“新建项目”对话框。 图3-4 创建Web应用项目Chapter3 在“类别”列表框中选中Java Web选项,在“项目”列表框中选中“Web应用程序”。单击对话框底部的“下一步”按钮,进入下一页面,如图3-5所示。 图3-5 设置Web应用项目名称和位置 在“项目名称”文本框输入Chapter3。单击“项目位置”右侧的“浏览”按钮可选择项目的位置。选中“设置为主项目”复选框将当前项目设置为主项目。单击底部的“下一步”按钮,进入下一页面,如图3-6所示。 图3-6 设置Web应用服务器 Web应用程序必须发布到Java EE Web服务器上才能够运行。在这里从“服务器”下拉列表框中选择NetBeans内置的服务器“GlassFish Server 3.1.1”,默认其他选项设置,单击“完成”按钮,则Web应用程序创建完毕。 下面为Web应用创建一个Servlet。在“项目”视图中选中Web应用程序Chapter3,右击,在弹出的快捷菜单中选择“新建”→Servlet命令,弹出如图3-7所示对话框。 图3-7 “新建Servlet”对话框 在“类名”文本框中输入Servlet实现类的名称First,在“包”文本框中输入Servlet实现类所在的包名com.servlet,单击“下一步”按钮,进入下一页面,如图3-8所示。 图3-8 配置Servlet 部署信息 这一步主要完成Servlet组件的部署配置,主要工作是设置Servlet的名称以及对应的URL模式名称。所谓URL模式,就是代表客户端请求的一个字符串,Web容器总是将匹配此字符串内容的请求转发到此Servlet组件来处理以便返回动态响应。“Servlet名称”文本框中的内容为Servlet的显示名称,并不要求等于前面定义的类名。在“Servlet名称”文本框中输入First。在“URL模式”文本框输入Servlet所对应的请求URL模式“/First”。选中“将信息添加到部署描述符(web.xml)”复选框,单击“完成”按钮,则一个名为First的Servlet组件创建完毕。NetBeans将在编辑器中自动打开Servlet的源代码。 在这个Servlet中,只要求Servlet在接收到请求后向客户端返回“Hello, World!”的提示信息。完整代码如程序3-1所示。 说明:为节省篇幅,代码中的一些注释信息被省略,完整的代码请到清华大学出版社的网站下载。另外,有些注释信息是NetBeans自动生成,由于机器环境的不同,读者所生成的注释信息可能与本书配套资源中的不完全一致,如 create date、author 信息等,这完全正常。 程序3-1:First.java package com.servlet; import java.io.*; import java.net.*; import javax.servlet.*; import javax.servlet.http.*; public class First extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try { out.println(""); out.println("
"); out.println("