第二章 servlet 2.4 简介

2-1 servlet 简介

自1997 年3月Sun Microsystems 公司所组成的JavaSoft部门将Servlet API 定案以来,推出

了Servlet API 1.0。就当时功能来说,Servlet 所提供的功能包含了当时的CGI (Common Gateway

Interface)与Netscape Server API (NSAPI)之类产品的功能。

发展至今,Servlet API的最新版本为2.4版。它依旧是一个具有跨平台特性、100% Pure Java

的Server-Side 程序,它相对于在Client 端执行的Applet。Servlet 不只限定于HTTP 协议,开发

人员可以利用Servlet自定义或延伸任何支持Java的Server —— 包括 Web Server、Mail Server、

Ftp Server、Application Server 或任何自定义的Server。

Servlet有以下优点:

● 可移植性(Portability)

Servlet 皆是利用Java 语言来开发的,因此,延续Java 在跨平台上的表现,不论Server 的操

作系统是Windows、Solaris、linux、HP-UX、FreeBSD、Compaq Tru 64、AIX 等等,都能够将我们

所写好的Servlet程序放在这些操作系统上执行。借助Servlet的优势,就可以真正达到Write Once,

Serve Anywhere 的境界,这正是从事Java 程序员最感到欣慰也是最骄傲的地方。

当程序员在开发Applet 时,时常为了“可移植性”(portability)让程序员感到绑手绑脚的,

例如:开发Applet 时,为了配合Client端的平台( 即浏览器版本的不同,plug-in 的JDK版本也

不尽相同 ),达到满足真正“跨平台”的目的时,需要花费程序员大量时间来修改程序,为的就是

能够让用户皆能够执行。但即使如此,往往也只能满足大部分用户,而其他少数用户,若要执行

Applet,仍须先安装合适的JRE (Java Runtime Environment)。

但是Servlet 就不同了,主要原因在于Servlet 是在Server 端上执行的,所以,程序员只要专

心开发能在实际应用的平台环境下测试无误即可。除非你是从事做Servlet Container 的公司,否

则不须担心写出来的Servlet 是否能在所有的Java Server 平台上执行。

强大的功能

Servlet 能够完全发挥Java API 的威力,包括网络和URL 存取、多线程(Multi-Thread)、影像

处理、RMI (Remote Method Invocation)、分布式服务器组件 (Enterprise Java Bean)、对象序列

化 (Object Serialization) 等。若想写个网络目录查询程序,则可利用JNDI API;想连接数据库

则可利用JDBC,有这些强大功能的API 做后盾,相信Servlet 更能够发挥其优势。

性能

Servlet 在加载执行之后,其对象实体(instance)通常会一直停留在Server 的内存中,若有请

求(request)发生时,服务器再调用Servlet 来服务,假若收到相同服务的请求时,Servlet会利用

不同的线程来处理,不像CGI 程序必须产生许多进程 (process)来处理数据。在性能的表现上,大

大超越以往撰写的CGI 程序。最后补充一点,那就是Servlet 在执行时,不是一直停留在内存中,

服务器会自动将停留时间过长一直没有执行的Servlet 从内存中移除,不过有时候也可以自行写程

序来控制。至于停留时间的长短通常和选用的服务器有关。

安全性

Servlet也有类型检查(Type Checking)的特性,并且利用Java的垃圾收集(Garbage Collection)

与没有指针的设计,使得Servlet 避免内存管理的问题。

由于在Java的异常处理(Exception-Handling)机制下,Servlet能够安全地处理各种错误,不会

因为发生程序上逻辑错误而导致整体服务器系统的毁灭。例如:某个Servlet发生除以零或其他不合

法的运算时,它会抛出一个异常(Exception)让服务器处理,如:记录在记录文件中(log file)。

2-2 First Servlet Sample Code

HelloServlet.java

package tw.com.javaworld.CH2;

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class HelloServlet extends HttpServlet {

//Initialize global variables

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

//Process the HTTP Get request

public void doGet(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

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

PrintWriter out = response.getWriter();

out.println("<html>");

out.println("<head><title>CH2 - HelloServlet</title></head>");

out.println("<body>");

out.println(" Hello World <br>");

out.println("大家好");

out.println("</body>");

out.println("</html>");

out.close();

}

//Get Servlet information

public String getServletInfo() {

return "tw.com.javaworld.CH2.HelloSerlvet Information";

}

}

注意:HelloServlet.java 范例程序位于JSPBook\WEB-INF\src\tw\com\javaworld\CH2,其中

JSPBook 范例程序的安装方法,请参见1-3节“安装JSPBook站台范例” 和1-4 节“安装Ant 1.6”。

一开始我们必须导入(import) javax.servlet.*、javax.servlet.http.*。

javax.servlet.* 存放与HTTP 协议无关的一般性Servlet 类;

javax.servlet.http.* :除了继承javax.servlet.* 之外,并且还增加与HTTP协议有关的功能。

所有Servlet 都必须实现javax.servlet.Servlet 接口(Interface),但是通常我们都会从

javax.servlet.GenericServlet 或javax.servlet.http.HttpServlet 择一来实现。若写的Servlet

程序和HTTP 协议无关,那么必须继承GenericServlet 类;若有关,就必须继承HttpServlet 类。

javax.servlet.* 里的ServletRequest和ServletResponse接口提供存取一般__________的请求和响应;

而javax.servlet.http.* 里的HttpServletRequest 和HttpServletResponse 接口,则提供HTTP

请求及响应的存取服务。

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

这个例子中,一开始和Applet 一样,也有init( )的方法。当Servlet 被Container 加载后,

接下来就会先执行init( )的内容,因此,我们通常利用init( )来执行一些初始化的工作。

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException,IOException {

response.setContentType("text/html");

PrintWriter out = response.getWriter();

out.println("<html>");

out.println("<head><title>CH2 - HelloServlet</title></head>");

out.println("<body>");

out.println("Hello World <br>");

out.println("大家好");

out.println("</body>");

out.println("</html>");

out.close();

}

Servlet 可以利用HttpServletResponse 类的setContentType( )方法来设定内容类型,我们要

显示为HTML 网页类型,因此,内容类型设为"text/html",这是HTML 网页的标准MIME 类型值。之

后,Servlet用getWriter( )方法取得PrintWriter类型的out对象,它与PrintSteam 类似,但是

它能对Java 的Unicode 字符进行编码转换。最后,再利用out 对象把"Hello World" 的字符串显

示在网页上。

public void destroy( ){

…………

…………. Servlet 结束时,会自动调用执行的程序

………….

}

若当Container 结束Servlet时,会自动调用destroy( ),因此,我们通常利用destroy( )来

关闭资源或是写入文件,等等。

编译 HelloServlet.java 的方法:

(1) 将servlet-api.jar 加入至CLASSPATH之中,直接使用javac 来编译HelloServlet.java。

其中servlet-api.jar 可以在{Tomcat_Install}\common\lib 找到。

(2) 直接使用Ant 方式编译HelloServlet.java,请参见1-4 节“安装Ant 1.6”。

编译好HelloServlet.java 之后,再来设定web.xml,如下:

<servlet>

<servlet-name>HelloServlet</servlet-name>

<servlet-class>tw.com.javaworld.CH2.HelloServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>HelloServlet</servlet-name>

<url-pattern>/HelloServlet</url-pattern>

</servlet-mapping>

最后,HelloServlet.java 的执行结果如图2-1 所示。

图 2-1 HelloServlet.java 的执行结果

顺利完成第一个Servlet 程序后,不知道读者有没有发现,在HelloServlet.java 主程序中,

其实大部分都是一些用来显示HTML 的out.println("….")程序代码,这就是Servlet 用在开发

Web-based 系统时最麻烦的地方。假若Servlet 要显示表格统计图时,我想那时候程序员一定会疯

掉,因为你会发现,其实你所有的时间都在out.println( ),因此,Servlet 适合在简单的用户接

口(User Interface)系统中。不过,幸好有JSP 技术来解决这项极为不方便的问题。

2-3 Servlet 的生命周期

当 Servlet加载Container 时,Container可以在同一个JVM 上执行所有Servlet,所以Servlet

之间可以有效地共享数据,但是Servlet 本身的私有数据亦受Java 语言机制保护。

Servlet 从产生到结束的流程如图2-2 所示。

(1) 产生Servlet,加载到Servlet Engine中,然后调用init( )这个方法来进行初始化工作。

(2) 以多线程的方式处理来自Client 的请求。

(3) 调用destroy( )来销毁Servlet,进行垃圾收集 (garbage collection)。

Servlet 生命周期的定义,包括如何加载、实例化、初始化、处理客户端请求以及如何被移除。

这个生命周期由javax.servlet.Servlet 接口的init ( )、service( )和destroy( )方法表达。

图 2-2 Servlet 从产生到结束的流程

1. 加载和实例化

当Container一开始启动,或是客户端发出请求服务时,Container会负责加载和实例化一个Servlet。

2. 初始化

Servlet 加载并实例化后,再来Container必须初始化Servlet。初始化的过程主要是读取配置

信息(例如JDBC连接)或其他须执行的任务。我们可以借助ServletConfig 对象取得Container的

配置信息,例如:

<servlet>

<servlet-name>HelloServlet</servlet-name>

<servlet-class>tw.com.javaworld.CH2.HelloServlet</servlet-class>

<init-param>

<param-name>user</param-name>

<param-value>browser</param-value>

</init-param>

</servlet>

其中user 为初始化的参数名称;browser 为初始化的值。因此,可以在HelloServlet程序中使

用ServletConfig 对象的getInitParameter("user")方法来取得browser。

3. 处理请求

Servlet 被初始化后,就可以开始处理请求。每一个请求由ServletRequest 对象来接收请求;

而ServletResponse 对象来响应该请求。

4. 服务结束

当 Container 没有限定一个加载的Servlet 能保存多长时间,因此,一个Servlet 实例可能只

在Container 中存活几毫秒,或是其他更长的任意时间。一旦destroy( )方法被调用时,Container

将移除该Servlet,那么它必须释放所有使用中的任何资源,若Container 需要再使用该Servlet

时,它必须重新建立新的实例。

2-4 Servlet 范例程序

为了说明Servlet 和网页是如何沟通的,笔者在此举一个Sayhi 的范例程序。这个范例程序分

为两部分:Sayhi.html 和Sayhi.java。

在 Sayhi.html 中,用户可以填入姓名,然后按下【提交】后,将数据传到Sayhi.java做处理,

而Sayhi.java 负责将接收到的数据显示到网页上。

Sayhi.html

<html>

<head>

<title>CH2 - Sayhi.html</title>

<meta http-equiv="Content-Type" content="text/html; charset=GB2312">

</head>

<body>

<h2>Servlet 范例程序</h2>

<form name="Sayhi" Method="Post" action="/JSPBook/CH2/Sayhi" >

<p>请访问者输入姓名:<input type="text" name="Name" size="30"></p>

<input type="submit" value="提交">

<input type="reset" value="清除">

</form>

</body>

</html>

Sayhi.html 的执行结果如图2-3 所示。

图 2-3 Sayhi.html 的执行结果

Sayhi.java

package tw.com.javaworld.CH2;

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class Sayhi extends HttpServlet {

//Initialize global variables

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

//Process the HTTP Get request

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

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

PrintWriter out = response.getWriter();

request.setCharacterEncoding("GB2312");

String Name = request.getParameter("Name");

out.println("<html>");

out.println("<head><title>CH2 - Sayhi</title></head>");

out.println("<body>");

out.println("Hi:"+Name);

out.println("</body>");

out.println("</html>");

out.close();

}

//Get Servlet information

public String getServletInfo() {

return "tw.com.javaworld.CH2.Sayhi Information";

}

public void destroy() {

}

}

从Sayhi.java的程序当中,可以发现Servlet是利用HttpServletRequest类的getParameter( )

方法来取得由网页传来的数据。不过数据通过HTTP协议传输时会被转码,因此在接收时,必须再做转

码的工作,才能够正确地接收到数据。下面这段程序是做转码的动作:

request.setCharacterEncoding("GB2312");

编译Sayhi.java 之后,再来设定web.xml:

<servlet>

<servlet-name>Sayhi</servlet-name>

<servlet-class>tw.com.javaworld.CH2.Sayhi</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>Sayhi</servlet-name>

<url-pattern>/CH2/Sayhi</url-pattern>

</servlet-mapping>

执行http://localhost:8080/JSPBook/CH2/Sayhi,结果如图2-4 所示。

图 2-4 Sayhi.html 按下【提交】后,经过Sayhi.java 处理后的结果

2-5 Servlet 2.4 的新功能

2003 年11 月底,J2EE 1.4 规范正式发布,Servlet也从原本的2.3版升级至2.4版。其中主要

新增的功能有以下三点:

(1) web.xml DTD 改用XML Schema;

(2) 新增Filter 四种设定;

(3) 新增Request Listener、Event 和Request Attribute Listener、Event。

2-5-1 web.xml 改用XML Schema

Servlet 在2.4 版之前,web.xml 都是使用DTD(Document Type Definition)来定义XML 文件内

容结构的,因此,Servlet 2.3 版 web.xml 一开始的声明如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

…………

</web-app>

到了Servlet 2.4 版之后,web.xml 改为使用XML Schema,此时web.xml 的声明如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

version="2.4">

…………

</web-app>

由DTD 改为Schema,主要加强两项功能:

(1) 元素可不依照顺序设定;

(2) 更强大的验证机制。

下面的范例,在Servlet 2.3 版是不合规则的web.xml 文件:

<web-app>

...

<servlet>

<servlet-name>ServletA</servlet-name>

<servlet-class>tw.com.javaworld.servlet.ServletA</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletA</servlet-name>

<url-pattern>/ServletA/*</url-pattern>

</servlet-mapping>

<servlet>

<servlet-name>ServletB</servlet-name>

<servlet-class> tw.com.javaworld.servlet.ServletB</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletB</servlet-name>

<url-pattern>/ServletB /*</url-pattern>

</servlet-mapping>

...

</web-app>

因为<servlet-mapping>元素必须在<servlet>元素之后,因此,上述的范例要改为:

<web-app>

...

<servlet>

<servlet-name>ServletA</servlet-name>

<servlet-class>tw.com.javaworld.servlet.ServletA</servlet-class>

</servlet>

<servlet>

<servlet-name>ServletB</servlet-name>

<servlet-class> tw.com.javaworld.servlet.ServletB</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletA</servlet-name>

<url-pattern>/ServletA/*</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>ServletB</servlet-name>

<url-pattern>/ServletB /*</url-pattern>

</servlet-mapping>

...

</web-app>

不过在Servlet 2.4版之后,原来的范例也算是一个合法的web.xml文件,不再须注意元素的顺序。

除此之外,Servlet 2.4 版web.xml 的Schema 更能提供强大的验证机制,例如:

(1) 可检查元素的值是否为合法的值。例如:<filter-mapping>的<dispatcher>元素,其值只

能为REQUEST、FORWARD、INCLUDE 和ERROR,如下所示:

<filter-mapping>

<filter-name>Hello</filter-name>

<url-pattern>/CH11/*</url-pattern>

<dispatcher>REQUEST</dispatcher>

<dispatcher>FORWARD</dispatcher>

</filter-mapping>

若<dispatcher>元素的值不为上述四种时,此web.xml 将会发生错误。

(2) 可检查如Servlet、Filter 或EJB-ref 等等元素的名称是否惟一。例如:

<servlet>

<servlet-name>ServletA</servlet-name>

<servlet-class>tw.com.javaworld.servlet.ServletA</servlet-class>

</servlet>

<servlet>

<servlet-name>ServletA</servlet-name>

<servlet-class>tw.com.javaworld.servlet.ServletB</servlet-class>

</servlet>

(3) 可检查元素值是否为合法文字字符或数字字符。例如:

<filter-mapping>

<filter-name>Hello</filter-name>

<url-pattern>/CH11/*</url-pattern>

</filter-mapping>

2-5-2 新增Filter 四种设定

Servlet 2.3 版新增了Filter的功能,不过它只能由客户端发出请求来调用Filter,但若使用

RequestDispatcher.forward( )或RequestDispatcher.include( )的方法调用Filter 时,Filter

却不会执行。因此,在Servlet 2.4版中,新增Filter的设定<dispatcher>来解决这个问题。有关

Filter 的部分在本书“第十一章:Filter 与Listener”有更详细的介绍。

Servlet 2.4 版新增的Filter 四种设定为:REQUEST、FORWARD、INCLUDE 和ERROR。假若你有

一个SimpleFilter,它只允许由客户端发出请求或由RequestDispatcher.include( )的方式来调用

执行SimpleFilter,此时SimpleFilter 的设定如下:

<filter>

<filter-name>SimpleFilter</filter-name>

<filter-class>tw.com.javaworld.CH11.SimpleFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>SimpleFilter</filter-name>

<url-pattern>/CH11/*</url-pattern>

<dispatcher>REQUEST</dispatcher>

<dispatcher>INCLUDE</dispatcher>

</filter-mapping>

2-5-3 新增Request ListenerEvent Request Attribute ListenerEvent

在Servlet 2.3 版中,新增许多的Listener 接口和Event 类(见表2-1):

表 2-1

Listener 接口Event 类

ServletContextListener ServletContextEvent

ServletContextAttributeListener ServletContextAttributeEvent

HttpSessionListener HttpSessionEvent

HttpSessionActivationListener

HttpSessionAttributeListener

在Servlet 2.4版陆续又多新增Request Listener、Event和Request Attribute Listener、Event

(见表2-2):

表 2-2

Listener 接口Event 类

ServletRequestListener ServletRequestEvent

ServletRequestAttributeListener ServletRequestAttributeEvent

这部分在“第十一章:Filter 与Listener”中有更详细的介绍。

2-5-4 Servlet 2.4 的其他变更

Servlet 2.4 其他较显著的变更如:

(1) 取消SingleThreadModel接口。当Servlet实现SingleThreadModel 接口时,它能确保同时间

内,只能有一个thread 执行此Servlet。

(2) <welcome-file-list>可以为Servlet。例如:

<servlet>

<servlet-name>Index</servlet-name>

<servlet-class>tw.com.javaworld.IndexServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>Index</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>Index</welcome-file>

</welcome-file-list>

(3) ServletRequest 接口新增一些方法,如:

public String getLocalName( );

public String getLocalAddr( );

public int getLocalPort( );

public int getRemotePort( );