Java Web程序设计
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

理论篇

第1章 Servlet基础

本章目标

■ 了解动态网站开发的相关技术

■ 理解Servlet的运行原理及生命周期

■ 掌握Servlet的编写及部署

■ 掌握Servlet对表单数据的处理

■ 掌握Servlet对HTTP请求报头的处理

学习导航

任务描述

【描述1.D.1】

使用Servlet输出“Hello World”页面。

【描述1.D.2】

使用Servlet处理表单数据,当用户提交的数据正确时(用户名haier,密码soft),输出“登录成功!”,否则提示“登录失败!”。

【描述1.D.3】

使用request对象读取报头信息,并打印在页面中。

【描述1.D.4】

通过设置响应报头,实现动态时钟。

【描述1.D.5】

使用请求重定向和转发两种方式,使用户自动访问重定向后的页面,区分转发和重定向的区别。

1.1 动态网站技术概述

在《Web编程基础》课程中已经学习使用HTML、CSS、JavaScript等相关技术来建设静态网站,其中,静态网页文件的扩展名为“.htm”或“.html”,这些页面不能与服务器进行数据交互。随着站点内容和功能需求的不断复杂化,单一的静态网站技术往往不能满足应用的要求。例如,静态网站无法完成将数据传输到服务器上进行处理并存储到数据库中,此时就需要动态网站技术。

1.1.1 动态网站技术

动态网站并不是指具有动画功能的网站,而是指基于数据库架构的网站,一般由大量的动态网页(如JSP)、后台处理程序(如Servlet)和用于存储内容的数据库组成。所谓“动态网页”,本质上与网页上的各种动画、滚动字幕等视觉上的“动态效果”无关。动态网页可以是纯文字内容,也可以包含各种动画内容,这些只是网页具体内容的表现形式。无论网页是否具有动态效果,采用动态网站技术生成的网页都称为动态网页。

动态网站一般具有以下几个特点。

交互性:网页会根据用户的要求和选择而动态改变和响应。例如,用户在网页中填写表单信息并提交,服务器可以对数据进行处理并保存到数据库中,然后跳转到相应页面。因此,动态网站可以实现用户注册、信息发布、订单管理等功能。

自动更新:无须手动更新HTML文档,便会自动生成新的页面,大大减少了工作量。例如,在论坛中发布信息时,后台服务器可以产生新的网页。

随机性:在不同的时间、不同的用户访问同一网页时可能产生不同的页面。

注意 动态网站一般采用动静结合的原则:网站中内容频繁更新的,可采用动态网页技术;网站中内容不需要更新的,则可采用静态网页进行显示。通常一个网站既包含动态网页也包含静态网页。

动态网站技术有早期的CGI技术,全名Common Gateway Interface(公用网关接口)。CGI提供了一种机制,可以实现客户和服务器之间真正的双向交互。这种技术为诸如在线用户支持和电子商务等新思想的实现铺平了道路,同时CGI技术因可以使用不同的语言编写适合的CGI程序,如Visual Basic、Delphi和C/C++等,并且功能强大,被早期的很多网站采用。但由于编程困难、效率低下、修改复杂,所以逐渐被新技术所取代。目前被广泛应用的动态网站技术主要有以下三种。

PHP(Hypertext Preprocessor):是超文本预处理器,其语法大量借鉴了C、Java、Perl等语言,只需要很少的编程知识就能使用PHP建立一个真正交互的Web站点。由于PHP开放源代码,并且是免费的,所以非常流行,是当今Internet上最为火热的脚本语言之一。

ASP(Active Server Pages):是一种类似HTML、Script与CGI结合体的技术,它没有提供自己专门的编程语言,允许用户使用许多已有的脚本语言编写ASP应用程序。但ASP技术局限于微软的操作系统平台之上,主要工作环境为微软的IIS应用程序结构,而且ASP技术不能很容易地实现在跨平台Web服务器上工作,因此一般只适合一些中小型站点。但目前由ASP升级演变而来的ASP.NET支持大型网站的开发。

JSP(Java Server Pages):是基于Java Servlet以及整个Java体系的Web开发技术。JSP是由SUN公司于1999年6月推出的新技术,它与ASP有一定的相似之处,但JSP能在大部分的服务器上运行,而且其应用程序易于维护和管理,安全性能方面也被认为是这三种基本动态网站技术中最好的。

1.1.2 B/S架构

在动态网站技术中,一般使用浏览器作为客户端,当客户在浏览器中发出请求时,Web服务器得到请求后查找资源,然后向客户返回一个结果,这就是B/S(Browser/Server)架构,如图1-1所示。

图1-1 B/S模型

在B/S架构中,用户的请求与Web服务器响应需要通过Internet网络从一台计算机发送到另一台计算机,不同计算机之间是使用HTTP(HyperText Transfer Protocol)协议进行通信的。HTTP是超文本传输协议,包含命令和传输信息,不仅用于Web访问,也可以用于其他互联网/内联网应用系统之间的通信,从而实现各种资源信息的超媒体访问集成。

1.2 Servlet简介

Servlet是JavaEE架构中的关键组成部分。JavaEE是基于分布式和多层结构的企业级应用开发规范和标准。目前,在企业应用开发中不仅会使用传统的JavaEE组件(例如JDBC、Servlet、EJB等),还会使用一些轻量级的框架结构(例如Struts、Hibernate和Spring),以提高企业开发效率。在Java企业级开发应用中会使用到的技术如图1-2所示。Servlet技术是Sun公司提供的一种实现动态网页的解决方案,它是基于Java编程语言的Web服务器端编程技术,主要用于在Web服务器端获得客户端的访问请求信息并动态生成对客户端的响应信息。此外,Servlet技术也是JSP技术的基础。

图1-2 JavaEE技术组成

Servlet是Web服务器端的Java应用程序,它支持用户交互式地浏览和修改数据,生成动态的Web页面。比如,当浏览器发送一个请求到服务器后,服务器会把请求送往一个特定的Servlet,这样Servlet就能处理请求并构造一个合适的响应(通常以HTML网页形式)返回给客户,如图1-3所示。

图1-3 Servlet的作用

Servlet与普通Java程序相比,只是输入信息的来源和输出结果的目标不一样,例如,对于Java程序而言,用户一般通过GUI窗口输入信息,并在GUI窗口上获得输出结果,而对于Servlet程序而言,用户一般通过浏览器输入并获取响应结果。通常普通Java程序所能完成的大多数任务,Servlet程序都可以完成。Servlet程序具有以下特点:

高效

在传统CGI中,如果有N个并发的对同一CGI程序的请求,则该CGI程序的代码在内存中重复装载了N次;而对于Servlet,处理请求的是N个线程,只需要一份Servlet类代码。在性能优化方面,Servlet也比CGI有着更多的选择,比如缓冲以前的计算结果、保持数据库连接的活动等。

方便

Servlet提供了大量的实用工具例程,例如自动地解析和解码HTML表单数据、读取和设置HTTP头、处理Cookie、跟踪会话状态等。

功能强大

在Servlet中,许多使用传统CGI程序很难完成的任务都可以轻松地完成。例如,Servlet能够直接和Web服务器交互,而普通的CGI程序不能。Servlet还能够在各个程序之间共享数据,很容易地实现数据库连接池之类的功能。

■ 良好的可移植性

Servlet是用Java语言编写的,所以具备Java的可移植性特点。此外,Servlet API具有完善的标准,支持Servlet规范的容器都可以运行Servlet程序,例如Tomcat、Resin等。

1.3 第一个Servlet

编写Servlet需要遵循其规范:

■ 创建Servlet时,需要继承HttpServlet类,同时需要导入Servlet API的两个包:javax.servlet和javax.servlet.http。javax.servlet包提供了控制Servlet生命周期所必需的Servlet接口,是编写Servlet时必须要实现的;javax.servlet.http包提供了从Servlet接口派生出的专门用于处理HTTP请求的抽象类和一般的工具类。

■ 根据数据的发送方式,覆盖doGet()、doPost()方法之一或全部。doGet()和doPost()方法都有两个参数,分别为HttpServletRequest和HttpServletResponse类型。这两个参数分别用于表示客户端的请求和服务器端的响应。通过HttpServletRequest,可以从客户端获得发送过来的信息;通过HttpServletResponse,可以让服务器端对客户端做出响应,最常用的就是向客户端发送信息。关于这两个参数,将在后续内容中详细讲解。

注意 如果在浏览器中直接输入地址来访问Servlet资源,属于使用GET方式访问。

下述代码用于实现任务描述1.D.1,使用Servlet输出“Hello World”页面。

【描述1.D.1】HelloServlet.java

    // 创建一个Servlet类,继承HttpServlet
    public class HelloServlet extends HttpServlet
    {
        // 重写doGet()
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            // 设置响应到客户端的文本类型为HTML
            response.setContentType("text/html");
            // 获取输出流
            PrintWriter out = response.getWriter();
            out.println(" Hello World");
        }
    }

上述代码会向客户端浏览器中打印“Hello World”信息。通过response对象的getWriter()方法可以获取向客户端输出信息的输出流:

    PrintWriter out = response.getWriter();

调用输出流的println()方法可以在客户端浏览器中打印消息。例如:

    out.println(" Hello World");

下面在Web应用的部署文件web.xml中注册此Servlet信息。

【描述1.D.1】在web.xml中配置Servlet

    <servlet>
        <display-name>Hello</display-name>
        <!-- Servlet的引用名 -->
        <servlet-name>Hello</servlet-name>
        <!-- 所配置的Servlet类的完整类路径 -->
        <servlet-class>com.haiersoft.ch01.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <!-- 前面配置的Servlet引用名-->
        <servlet-name>Hello</servlet-name>
        <!-- 访问当前Servlet的URL -->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

在上述配置信息中,需要注意以下几个方面:

■ Servlet别名,即<servlet-name>和</servlet-name>之间的命名可以随意命名,但要遵循命名规范。

■ <servlet>和<servlet-mapping>元素可以配对出现,通过Servlet别名进行匹配。<servlet>元素也可以单独出现,通常用于初始化操作。

■ URL引用,即<url-pattern>和</url-pattern>之间的命名通常以“/”开头。

启动Tomcat,在IE中访问http://localhost:8080/ch01/hello,运行结果如图1-4所示。

图1-4 运行结果

注意 关于Web应用的开发过程及配置,可参考实践1.G.2指导部分。

1.4 Servlet的生命周期

Servlet是运行在服务器上的,其生命周期由Servlet容器负责。Servlet生命周期是指Servlet实例从创建到响应客户请求直至销毁的过程。Servlet API中定义了关于Servlet生命周期的3个方法。

init():用于Servlet初始化。当容器创建Servlet实例后,会自动调用此方法。

service():用于服务处理。当客户端发出请求,容器会自动调用此方法进行处理,并将处理结果响应到客户端。service()方法有两个参数,分别接收ServletRequest接口和ServletResponse接口的对象来处理请求和响应。

destroy():用于销毁Servlet。当容器销毁Servlet实例时自动调用此方法,释放Servlet实例,清除当前Servlet所持有的资源。

Servlet生命周期概括为以下几个阶段。

01 装载Servlet:这项操作一般是动态执行的,有些服务器提供了相应的管理功能,可以在启动的时候就装载Servlet。

02 创建一个Servlet实例:容器创建Servlet的一个实例对象。

03 初始化:容器调用init()方法对Servlet实例进行初始化。

04 服务:当容器接收到对此Servlet的请求时,将调用service()方法响应客户的请求。

05 销毁:容器调用destroy()方法销毁Servlet实例。

在Servlet生命周期的这几个阶段中,初始化init()方法仅执行一次,是在服务器装载Servlet时执行的,以后无论有多少客户访问此Servlet,都不会重复执行init()。即此Servlet在Servlet容器中只有单一实例;当多个用户访问此Servlet时,会分为多个线程访问此Servlet实例对象的service()方法。在service()方法内,容器会对客户端的请求方式进行判断,如果是Get方式提交,则调用doGet()进行处理;如果是Post方式提交,则调用doPost()进行处理。图1-5说明了Servlet生命周期的不同阶段。

下面代码演示了Servlet的生命周期。

图1-5 Servlet的生命周期

【代码1-1】ServletLife.java

    public class ServletLife extends HttpServlet {
        /**
        * 构造方法
        */
        public ServletLife() {
            super();
        }
        /**
        * 初始化方法
        */
        public void init(ServletConfig config) throws ServletException {
            System.out.println("初始化时,init()方法被调用!");
        }
        protected void doGet(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            System.out.println("处理请求时,doGet()方法被调用。");
        }
        protected void doPost(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            System.out.println("处理请求时,doPost()方法被调用。");
        }
        /**
        * 用于释放资源
        */
        public void destroy() {
            super.destroy();
            System.out.println("释放系统资源时,destroy()方法被调用!");
        }
    }

启动Tomcat,在IE中访问http://localhost:8080/ch01/ServletLife,观察控制台输出信息,如图1-6所示。

图1-6 Servlet生命周期

打开多个IE窗口,访问此Servlet,观察控制台输出,会发现init()方法只运行一次,而service()方法会对每次请求都做出响应。

1.5 Servlet数据处理

Servlet数据处理主要包括读取表单数据、HTTP请求报头的处理和HTTP响应报头的设置。

1.5.1 读取表单数据

当访问Internet网站时,在浏览器地址栏中会经常看到如下所述的字符串:

    http://host/path?usr=tom&dest=ok

该字符串问号后面的部分为表单数据(Form Data)或查询数据(Query Data),这些数据以“name=value”形式通过URL传送,多个数据使用“&”分开,这种形式也称为“查询字符串”。查询字符串紧跟在URL中的“?”后面,所有“名/值”对会被传递到服务器,这是服务器获取客户端信息所采用的最常见的方式。

表单数据可以通过GET请求方式提交给服务器,此种方式将数据跟在问号后附加到URL的结尾(查询字符串形式);也可以采用POST请求方式提交给服务器,此种方式将在地址栏看不到表单数据信息,可用于大量数据的传输,并且比GET方式更安全。

在学习处理Form表单数据前,先来回顾《Web编程基础》中学过的关于表单的基本知识。

(1)使用Form标签创建HTML表单。

使用action属性指定对表单进行处理的Servlet或JSP页面的地址,可以使用绝对或相对URL。例如:

    <form action="...">...</form>

如果省略action属性,那么数据将提交给当前页面对应的URL。

(2)使用输入元素收集用户数据。

将这些元素放在Form标签内,并为每个输入元素赋予一个name。文本字段是最常用的输入元素,其创建方式如下:

    <input type="text" name="...">

(3)在接近表单的尾部放置提交按钮。

例如:

    <input type="submit"/>

单击提交按钮时,浏览器会将数据提交给表单action对应的服务器端程序。

1. Form表单数据

通过HttpServletRequest对象可以读取Form标签中的表单数据。HttpServletRequest接口在javax.servlet.http包中定义,它扩展了ServletRequest,并定义了描述一个HTTP请求的方法。当客户端请求Servlet时,一个HttpServletRequest类型的对象会被传递到Servlet的service()方法,进而传递到doGet()或doPost()方法中去。此对象中封装了客户端的请求扩展信息,包括HTTP方法(即GET或POST)、Cookie、身份验证和表单数据等信息。

表1-1列出了HttpServletRequest接口中用于读取表单数据的方法。

表1-1 HttpServletRequest接口中读取表单数据的方法

默认情况下,request.getParameter()使用服务器的当前字符集解释输入。要改变这种默认行为,需要使用setCharacterEncoding(String env)方法来设置字符集,例如:

    request.setCharacterEncoding("GBK");

下述内容用于实现任务描述1.D.2,使用Servlet处理表单数据,当用户提交的数据正确时(用户名haier,密码soft),输出“登录成功!”,否则提示“登录失败!”。

(1)首先编写静态页面,用于接收用户信息。

【描述1.D.2】index.html

    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=gbk">
    <title>登录</title>
    <script language="javascript" type="">
            function LoginSubmit(){
                var user=document.Login.loginName.value;
                var pass=document.Login.password.value;
                if(user==null||user==""){
                    alert("请填写用户名");
                }
                else if(pass==null||pass==""){
                    alert("请填写密码");
                }
                else document.Login.submit();
            }
    </script>
    </head>
    <body>
    <form method="POST" name="Login" action="LoginServlet">
      <p align="left">
      用户名:<input type="text" name="loginName" size="20"></p>
      <p align="left">
      密&nbsp; 码:<input type="password" name="password" size="20"></p>
      <p align="left">
      <input type="button" value="提交" name="B1" onclick="LoginSubmit()">
      <input type="reset" value="重置" name="B2"></p>
    </form>
    </body>
    </html>

上述HTML代码中,使用JavaScript对用户表单进行初始验证,验证成功后才提交给LoginSevlet进行处理。

(2)编写Servlet处理用户提交表单数据。

【描述1.D.2】LoginServlet.java

    public class LoginServlet extends HttpServlet {
        public LoginServlet() {
            super();
        }
    public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doPost(request, response);
        }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            // 设置请求的编码字符为GBK(中文编码)
            request.setCharacterEncoding("GBK");
            // 设置响应的文本类型为html,编码字符为GBK
            response.setContentType("text/html;charset=GBK");
            // 获取输出流
            PrintWriter out = response.getWriter();
            // 获取表单数据
            String pass = request.getParameter("password");
            String user = request.getParameter("loginName");
            if ("haier".equals(user) && "soft".equals(pass)) {
                out.println("登录成功!");
            } else {
                out.println("登录失败!");
            }
    }
    }

上述代码中,在doGet()方法中调用了doPost()方法,这样不管用户以什么方式提交,处理过程都一样。因为页面中使用了中文,为了防止出现中文乱码问题,所以需要设置请求和响应的编码字符集,使之能够支持中文,如下所示:

    request.setCharacterEncoding("GBK");
    response.setContentType("text/html;charset=GBK");

获取表单中的数据时,使用getParameter()方法通过参数名获得参数值,例如:

    String pass = request.getParameter("password");

上面语句通过参数名“password”来获取该参数的值。

注意 如果index.html中表单的提交方式为GET方式,则在浏览器地址栏中会出现查询字符串形式的表单数据(如password=soft&user=haier),但在LoginServlet中获取参数值的方式完全相同。

(3)在web.xml中注册该Servlet。

【描述1.D.2】web.xml

    <servlet>
        <display-name>LoginServlet</display-name>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>com.haiersoft.ch01.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>/LoginServlet</url-pattern>
    </servlet-mapping>

上述代码中,注册了一个名为“LoginServlet”的Servlet,当请求的相对URL为“/LoginServlet”时,Servlet容器会将请求交给该Servlet进行处理。

启动Tomcat,在IE中访问http://localhost:8080/ch01/index.html,运行结果如图1-7所示。

图1-7 index.html页面

在“用户名”文本栏中输入“haier”,在“密码”文本框中输入“soft”。然后单击“提交”按钮,显示结果如图1-8所示。

当输入错误的用户名或密码时,则显示“登录失败!”,如图1-9所示。

图1-8 LoginServlet验证成功

图1-9 LoginServlet验证失败

Form表单数据中除了普通的表单项之外,在实际开发中会广泛应用到隐藏域。隐藏域是隐藏的HTML表单变量,可以用来存储状态信息,操作起来与一般的HTML输入域(比如文本输入域、复选框和单选按钮)类似,同样会被提交到服务器。隐藏域与普通的HTML输入域之间的不同之处就在于客户端不能看到或修改隐藏域的值。

隐藏域可以用来在客户端和服务器之间透明地传输状态信息,示例代码如下。

【代 码1-2】hidden.html

    <html>
    <head>
    <title>隐藏域</title>
    </head>
    <body bgcolor="blue">
    <form method="post" action="nameservlet">
    <p> 请输入用户名:<br>
    <input type="text" name="uname"><br>
    <input type="hidden" name="bcolor" value="blue"><br>
    <input type="submit" value="submit">
    </form>
    </body>
    </html>

上述代码中使用隐藏域将用户所喜欢的背景色传递给服务器。在服务器端获取隐藏域的数据与表单其他元素一样,都是使用getParameter()方法通过参数名获取其数据值。

2. 查询字符串

查询字符串是表单数据的另一种情况,它们实质上是相同的。同样,服务器端的Servlet也是通过HttpServletRequest对象的getParameter()方法或者getParameterValues()方法读取URL中查询字符串的信息,然后根据信息可以进行查询,再把查询的结果返回。

下述代码演示查询字符串的应用。

【代码1-3】querystr.html

    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=GBK">
    <title> 查询字符串</title>
    </head>
    <body>
    <a href="TestURL?id=2010">下一页</a>
    </body>
    </html>

上述代码中,在超链接的URL中使用查询字符串,在“?”后添加了“id=2010”,该语句传递了一个参数id,其值为2010。

【代码1-4】TestURL

    public class TestURL extends HttpServlet {
        public TestURL() {
            super();
        }
        protected void doGet(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
        protected void doPost(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=GBK");
            PrintWriter out = response.getWriter();
            String id = request.getParameter("id");
            out.println("URL参数值是:" + id);
        }
    }

在上述代码中,使用request对象的getParameter()方法获取URL中的参数值,并输出。

启动Tomcat,在IE中访问http://localhost:8080/ch01/querystr.html,显示结果如图1-10所示。

单击“下一页”超链接,显示结果如图1-11所示。

图1-10 index.html

图1-11 TestURL结果

1.5.2 处理HTTP请求报头

客户端浏览器向服务器发送请求的时候,除了用户输入的表单数据或者查询数据之外,通常还会在GET/POST请求行后面加上一些附加的信息;而在服务器向客户端的请求做出响应的时候,也会自动向客户端发送一些附加的信息。这些附加信息被称为HTTP报头,信息附加在请求信息后面称为HTTP请求报头,而附加在响应信息后面则称为HTTP响应报头。在Servlet中可以获取或设置这些报头的信息。

报头信息的读取比较简单:只需将报头的名称作为参数,调用HttpServletRequest的getHeader方法;如果当前的请求中提供了对应的报头信息,则返回一个String,否则返回null。

另外,这些报头的参数名称不区分大小写,也就是说,也可以通过getHeader("user-agent")来获得User-Agent报头。常用的HTTP请求报头如表1-2所示。

表1-2 常用HTTP请求报头

尽管getHeader()方法是读取输入报头的通用方式,但由于几种报头的应用很普遍,因此HttpServletRequest为它们提供了专门的访问方法,如表1-3所示。

表1-3 HttpServletRequest获取报头信息的方法

下述代码用于实现任务描述1.D.3,演示报头信息的读取方式。

【描述1.D.3】HttpHeadServlet.java

    public class HttpHeadServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
        protected void doPost(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=gbk");
            PrintWriter out = response.getWriter();
            StringBuffer buffer = new StringBuffer();
            buffer.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 "
                    + "Transitional//EN\">");
            buffer.append("<html>");
            buffer.append("<head><title>");
            String title = "请求表头信息";
            buffer.append(title);
            buffer.append("</title></head>");
            buffer.append("<body>");
            buffer.append("<h1 align='center'>" + title + "</h1>");
            buffer.append("<b>Request Method: </b>");
            buffer.append(request.getMethod() + "<br/>");
            buffer.append("<b>Request URL: </b>");
            buffer.append(request.getRequestURI() + "<br/>");
            buffer.append("<b>Request Protocol: </b>");
            buffer.append(request.getProtocol() + "<br/>");
            buffer.append("<b>Request Local: </b>");
            buffer.append(request.getLocale() + "<br/><br/>");
            buffer.append("<table border='1' align='center'>");
            buffer.append("<tr bgcolor='#FFAD00'>");
            buffer.append("<th>Header Name</th><th>Header Value</th>");
            buffer.append("</tr>");
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String headerName = (String) headerNames.nextElement();
                buffer.append("<tr>");
                buffer.append("<td>" + headerName + "</td>");
                buffer.append("<td>" + request.getHeader(headerName) + "</td>");
                buffer.append("</tr>");
            }
            buffer.append("</body>");
            buffer.append("</html>");
            out.println(buffer.toString());
        }
    }

上述代码中,通过调用request对象中的getMethod()方法来获取用户请求方式;调用getRequestURI()方法来获取用户请求路径;调用getHeaderNames()方法返回所有请求报头名称的集合,遍历此集合并使用getHeader()提取报头信息显示。

启动Tomcat,在IE中访问http://localhost:8080/ch01/HttpHeadServlet,运行结果如图1-12所示。

图1-12 请求报头信息

1.5.3 设置HTTP响应报头

在Servlet中,可以通过HttpServletResponse的setHeader()方法来设置HTTP响应报头,它接收两个参数,用于指定响应报头的名称和对应的值,语法格式如下:

    setHeader(String headerName,String headerValue)

常用的HTTP响应报头如表1-4所示。

表1-4 常用的HTTP响应报头

注意 一些旧版本的浏览器只支持HTTP 1.0的报头,所以,为了保证程序具有良好的兼容性,应该慎重地使用这些报头,或者使用HttpServletRequest的getRequestProtocol()方法获得HTTP的版本后再做选择。

除了setHeader()方法外,还有两个方法用于设置日期或者整型数据格式报头:

    setDateHeader(String headerName, long ms)

    setIntHeader(String headerName, int headerValue)

此外,对于一些常用的报头,在API中也提供了更方便的方法来设置它们,如表1-5所示。

表1-5 HttpServletResponse响应方法

下述代码用于实现任务描述1.D.4,通过设置响应报头,实现动态时钟。

【描述1.D.4】DateServlet.java

    public class DateServlet extends HttpServlet {
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException { // 获得一个向客户发送数据的输出流
            response.setContentType("text/html; charset=GBK");// 设置响应的MIME类型
            PrintWriter out = response.getWriter();
            out.println("<html>");
            out.println("<body>");
            response.setHeader("Refresh", "1"); // 设置Refresh的值
            out.println("现在时间是:");
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            out.println("<br/>" + sdf.format(new Date()));
            out.println("</body>");
            out.println("</html>");
        }
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doPost(request, response);
        }
    }

上述代码中,通过设置响应报头,使得客户端每隔一秒访问一次当前Servlet,从而在客户端能够动态地观察时钟的变化。实现每隔一秒动态刷新的功能代码如下:

    response.setHeader("Refresh", "1");

其中,Refresh为响应头部信息;1是时间间隔值,以秒为单位。

启动Tomcat,在IE中访问http://localhost:8080/ch01/DateServlet,运行结果如图1-13所示。

图1-13 动态时钟

注意 在任务描述1.D.4中,通过不断地刷新当前页面来访问DateServlet,从而实现了动态时钟,在本书第8章通过AJAX技术可以实现无刷新时钟,读者可以通过观察显示效果对这两种方式进行比较。

1.6 重定向和请求转发

重定向和请求转发是Servlet处理完数据后进行页面跳转的两种主要方式。

1.6.1 重定向

重定向是指页面重新定位到某个新地址,之前的Request失效,进入一个新的Request,且跳转后浏览器地址栏内容将变为新的指定地址。重定向是通过HttpServletResponse对象的sendRedirect()方法来实现的,该方法用于生成302响应码和Location响应头,从而通知客户端去重新访问Location响应头中指定的URL,其语法格式如下:

    pubilc void sendRedirect(java.lang.String location)throws java.io.IOException

其中:

■ location参数指定了重定向的URL,它可以是相对路径也可以是绝对路径。

使用sendRedirect()方法不仅可以重定向到当前应用程序中的其他资源,还可以重定向到其他应用程序中的资源,例如:

    response.sendRedirect("/ch01/index.html");

上面语句重定向到当前站点(ch01)的根目录下的index.html界面。

下述代码用于实现任务描述1.D.5,使用请求重定向方式使用户自动访问重定向后的页面。

【描述1.D.5】RedirectServlet.java

    public class RedirectServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html; charset="GBK");
            PrintWriter out = response.getWriter();
            out.println("重定向前");
            response.sendRedirect(request.getContextPath() + "/myservlet");
            out.println("重定向后");
        }
    }

在web.xml配置文件中配置RedirectServlet的<url-pattern>为“/redirect”。

其中,myservlet对应的Servlet代码如下所示。

【描述1.D.5】MyServlet.java

    public class MyServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            // 设置响应到客户端的文本类型为HTML
            response.setContentType("text/html; charset=GBK");
            // 获取输出流
            PrintWriter out = response.getWriter();
            out.println("重定向和请求转发");
        }
    }

在web.xml配置文件中配置MyServlet的<url-pattern>为“/myservlet”。

启动Tomcat,在IE中访问http://localhost:8080/ch01/redirect,显示出了MyServlet输出网页中的内容,这时浏览器地址栏中的地址变成了MyServlet的URL“http://localhost:8080/ch01/myservlet”,结果如图1-14所示。

图1-14 重定向地址栏变化

1.6.2 请求转发

请求转发是指将请求再转发到另一页面,此过程依然在Request范围内,转发后浏览器地址栏内容不变。请求转发使用RequestDispatcher接口中的forward()方法来实现,该方法可以把请求转发到另外一个资源,并让该资源对浏览器的请求进行响应。

RequestDispatcher接口有以下两个方法。

forward()方法:请求转发,可以从当前Servlet跳转到其他Servlet。

include()方法:引入其他Servlet。

RequestDispatcher是一个接口,通过使用HttpRequest对象的getRequestDispalcher()方法可以获得该接口的实例对象,例如:

    RequestDispatcher rd = request.getRequestDispatcher(path);
    rd.forward(request,response);

下述代码用于实现任务描述1.D.5,使用请求转发方式使用户自动访问请求转发后的页面。

【描述1.D.5】ForwardServlet.java

    //请求转发
    public class ForwardServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html; charset=GBK");
            PrintWriter out = response.getWriter();
            out.println("请求转发前");
            RequestDispatcher rd = request.getRequestDispatcher("/myservlet");
            rd.forward(request, response);
            out.println("请求转发后");
        }
    }

在IE中访问http://localhost:8080/ch01/forward,浏览器中显示出了MyServlet输出网页中的内容,这时浏览器地址栏中的地址不会发生改变,结果如图1-15所示。

图1-15 请求转发地址栏变化

通过上述ForwardServlet和RedirectServlet的运行结果可以看出,转发和重定向两种方式在调用后地址栏中的URL是不同的,前者的地址栏不变,后者地址栏中的URL变成目标URL。

此外,转发和重定向最主要的区别是:转发前后共享同一个request对象,而重定向前后不在一个请求中。

为了验证请求转发和重定向的区别,在示例中会用到HttpServletRequest的存取和读取属性值的两个方法。

getAttribute(String name):取得name的属性值,如果属性不存在则返回null。

setAttribute(String name,Object value):将value对象以name名称绑定到request对象中。

注意 除HttpServletRequest接口外,HttpSession和ServletContext接口也拥有getAttribute()和setAttribute()方法,分别用来读取和设置这两类对象中的属性值。

下述内容用于实现任务描述1.D.5,通过请求参数的传递来验证forward()方法和sendRedirect()方法在request对象共享上的区别。

(1)改写RedirectServlet,在sendRedirect()方法中加上查询字符串。

【描述1.D.5】RedirectServlet.java

    public class RedirectServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html; charset=GBK");
            PrintWriter out = response.getWriter();
            request.setAttribute("test","helloworld");
            out.println("重定向前");
            response.sendRedirect(request.getContextPath() + "/myservlet ");
            out.println("重定向后");
        }
    }

上述代码中,调用了setAttribute()方法把test属性值helloworld存储到request对象中。

(2)改写MyServlet,获取request对象中的test属性值。

【描述1.D.5】MyServlet.java

    public class MyServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            // 设置响应到客户端的文本类型为HTML
            response.setContentType("text/html; charset=GBK");
            String test =(String)request.getAttribute("test");
            // 获取输出流
            PrintWriter out = response.getWriter();
            out.println("重定向和请求转发");
            out.println(test);
        }
    }

上述代码中,从request对象中获取test属性值。

启动Tomcat,在IE中访问http://localhost:8080/ch01/redirect,运行结果如下:

    重定向和请求转发null

由此可知,在MyServlet中的request对象中并没有获得RedirectServlet中request对象设置的值。

(3)改写ForwardServlet,获取request对象中的test属性值。

【描述1.D.5】ForwardServlet.java

    // 请求转发
    public class ForwardServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html; charset=GBK");
            request.setAttribute("test","helloworld");
            PrintWriter out = response.getWriter();
            out.println("请求转发前");
            RequestDispatcher rd = request.getRequestDispatcher("/myservlet");
            rd.forward(request, response);
            out.println("请求转发后");
        }
    }

上述代码中,从request对象中获取test属性值。

启动Tomcat,在IE中访问http://localhost:8080/ch01/forward,运行结果如下:

    重定向和请求转发helloworld

由此可知,在MyServlet中的request对象中获得了ForwardServlet的request对象设置的值。

通过对上述示例的运行结果进行比较,forward()和sendRedirect()两者的区别总结如下:

■ forward()只能将请求转发给同一个Web应用中的组件,而sendRedirect()方法不仅可以重定向到当前应用程序中的其他资源,还可以重定向到其他站点中的资源。如果传给sendRedirect()方法的相对URL以“/”开头,它是相对于整个Web站点的根目录;如果创建RequestDispatcher对象时指定的相对URL以“/”开头,它是相对于当前Web应用程序的根目录。

■ sendRedirect()方法重定向的访问过程结束后,浏览器地址栏中显示的URL会发生改变,由初始的URL地址变成重定向的目标URL;而调用forward()方法的请求转发过程结束后,浏览器地址栏保持初始的URL地址不变。

■ forward()方法的调用者与被调用者之间共享相同的request对象和response对象,它们属于同一个请求和响应过程;而sendRedirect()方法调用者和被调用者使用各自的request对象和response对象,它们属于两个独立的请求和响应过程。

1.7 小结

通过本章的学习,学生应该能够学会:

■ 动态网站开发技术有Servlet、JSP、PHP、ASP、ASP.NET和CGI等。

■ Servlet是运行在服务器端的Java程序,内嵌HTML。

■ Servlet生命周期的三个方法分别是init()、service()和destroy()。

■ Servlet处理GET和POST请求时分别使用doGet()和doPost()方法进行处理。

■ HttpServletRequest的getParameter(“参数名称”)获取表单、URL参数值。

■ HttpServletResponse的getWriter()获取向客户端发送信息的输出流。

■ HttpServletRequest的getHeader(“报头名称”)获取相关报头信息。

■ 请求转发和重定向都可以使浏览器获得另外一个URL所指向的资源。

■ 请求转发通常由RequestDispatcher接口的forward()方法实现,转发前后共享同一个请求对象。

■ 重定向由HttpServletResponse接口的sendRedirect()方法实现,重定向不共享同一个请求对象。

练习

1. 下列选项中属于动态网站技术的是______。(多选)

A. PHP

B. ASP

C. JavaScript

D. JSP

2. 下列关于Servlet的说法正确的是______。(多选)

A. Servlet是一种动态网站技术

B. Servlet运行在服务器端

C. Servlet针对每个请求使用1个进程来处理

D. Servlet与普通的Java类一样,可以直接运行,不需要环境支持

3. 下列关于Servlet的编写方式正确的是______。(多选)

A. 必须是HttpServlet的子类

B. 通常需要覆盖doGet()和doPost()方法或其中之一

C. 通常需要覆盖service()方法

D. 通常需要在web.xml文件中声明<servlet>和<servlet-mapping>两个元素

4. 下列关于Servlet生命周期的说法正确的是______。(多选)

A. 构造方法只会调用一次,在容器启动时调用

B. init()方法只会调用一次,在第一次请求此Servlet时调用

C. service()方法在每次请求此Servlet时都会被调用

D. destroy()方法在每次请求完毕时会被调用

5. 下列方式中可以执行TestServlet(路径为 /test)的doPost()方法的是______。(多选)

A. 在IE中直接访问http://localhost:8080/网站名/test

B. <form action="/网站名/test"> 提交此表单

C. <form action="/网站名/test" method="post"> 提交此表单

D. <form id="form1">,在JavaScript中执行下述代码:

    document.getElementById("form1").action="/网站名/test";
    document.getElementById("form1").method="post";
    document.getElementById("form1").submit();

6. 针对下述JSP页面,在Servlet中需要得到用户选择的爱好的数量,最合适的代码是______。

    <input type="checkbox" name="aihao" value="1"/>游戏<br/>
    <input type="checkbox" name="aihao" value="2"/>运动<br/>
    <input type="checkbox" name="aihao" value="3"/>棋牌<br/>
    <input type="checkbox" name="aihao" value="4"/>美食<br/>

A. request.getParameter("aihao").length

B. request.getParameter("aihao").size()

C. request.getParameterValues("aihao").length

D. request.getParameterValues("aihao").size()

7. 用户使用POST方式提交的数据中存在汉字(使用GBK字符集),在Servlet中需要使用下述______语句处理。

A. request.setCharacterEncoding("GBK");

B. request.setContentType("text/html;charset=GBK");

C. response.setCharacterEncoding("GBK");

D. response.setContentType("text/html;charset=GBK");

8. 简述Servlet的生命周期。Servlet在第一次和第二次被访问时,其生命周期方法的执行有何区别?

9. 简述转发和重定向两种页面跳转方式的区别,在Servlet中分别使用什么方法实现?