Servlet开发详解
在本书前面的章节中经常提到Servlet,讲过JSP页面在执行之前会首先预编译为Servlet然后再运行。那么究竟什么是Servlet,Servlet和JSP是什么关系?本章将针对这些问题进行讲解,使读者能够理解JSP的运行原理,从而能够开发出更好更有效的JSP应用程序。
7.1 Servlet简介
7.1.1 Java服务器结构
本书中所讲的服务器不是指硬件服务器,而是从软件功能上来讲的。则从软件功能上讲,服务器指的就是能够提供服务的运行着的程序,即一个提供服务的进程。
“服务(Service)”就是一些相关功能的集合,这些功能响应基于公开的接口和行为规范,并通过封装其实现的接口,来响应特定的请求并产生相应的结果。常见的应用级协议包扩HTTP、FTP、SMTP以及TELNET等,这些都是标准化的网络协议。
那么从最简单的概念上讲,一个服务器就是这样一个程序:它首先能识别出符合标准协议格式的请求信息,其次再把请求信息进行内部处理,最后再将处理结果按照标准协议的格式发送回去。
建立了服务器的概念,然后就应该考虑Java是如何实现这样的架构的。
服务构架(Service Framework)是一个实现了服务的类的集合,这些服务使用多个处理线程来完成与客户的交互。服务器通过一个连接点(ConnectionEndpoint)描述符获得一个基于连接的服务的特定端口(ServerSocket)。ConnectionEndpoint描述符封装了一些特定的信息,例如服务所使用的监听端口号。选项和协议信息等。
获得了ServerSocket之后,服务就可以创建其自身的服务处理线程池。在线程处理池中的线程进入循环,等待来自客户端的连接请求。在受到请求后通过该连接链路处理协议。处理线程池的大小是动态变化的,当有需要时临时创建新的处理线程,但一般情况下数量有上下限限制。
核心的服务构架通常用来处理某个特定的应用协议。例如,为HTTP服务提供了一个HTTP服务基类,由其创建的处理线程就能够处理HTTP协议。
服务器在启动的时候要启动一个服务集合,每个服务都有其自己的线程组。除了处理标准协议的服务之外,服务器还要包含进行内部协调和管理的服务。
Servlet就是在Java中为实现以上所述的服务构架所产生的。
7.1.2 Servlet的功能
Servlet可称之为“服务器小程序”,与Java application不同,它没有main方法,而是用一些特定的方法用于启动、执行和退出。通过使用Servlet,可以与运行于客户端的Applet进行交互,也可以直接与HTML页进行交互。
与传统的CGI相比,Java Servlet在处理同样的事物时更高效、更强大,并且具有容易使用、容易移植的特点,使用费用也很低。
在传统的CGI模式中(运行在服务器上的外部程序、功能模块),对于每一个HTTP请求,服务器都要启动一个进程,如果这个请求只是要求一个很简单的操作,那么所花费的代价就几乎都表现在服务器进程的启动时间上了。而Servlets是建立在Java虚拟机上的,每一个请求对应一个更轻量级的Java线程,这样在处理请求时就更具有灵活性,比如可以在若干个请求中保留对其他资源的控制等。
Servlet除了具有Java本身的许多优点外,还提供一整套系统的数据结构自动分析功能,可以解析HTML表单的提交参数,读取和设置HTTP的头部信息,处理Cookie,用户Session追踪以及相关操作。
利用Servlets可以较容易的做到一些普通CGI很难做到甚至做不到的事情,比如直接与网络服务器对话等。不同的Servlet之间可以进行一些资源的共享,例如共享对数据库的连接等。
通过Servlet的安全模型和“沙箱”机制还可以保护系统不受破坏。
此外,由于Java本身的平台无关性,就使得基于Servlet的Java服务器又很强的适应能力,本书中所用到的Tomcat就可以与不同的网络服务器配合使用。
Sun公司为Servlet程序提供了一个标准的API(ServletAPI),保证了程序开发的一致性。ServletAPI的最大优点就是它的协议无关性,因此其中并不包含在网络上传输所用的协议、Servlet的加载方式以及服务器环境等内容,这样就使其容易被嵌入到多种类型的服务器中。
7.1.3 Servlet开发包
要进行Servlet开发首先要有开发工具,在前面讲解Java开发时讲了一些JDK的开发包。同样,开发Servlet时我们要用到Servlet开发包。
本书中主要讲解随JSWDK一起提供的几个Servlet开发包,对于其他或第三方包请读者查阅相关资料。JSWDK给出的Servlet开发包分别是javax.servlet、javax.servlet.http和javax.servlet.jsp。读者打开JSWDK安装目录下的“\webpages\docs\api\index.html”文件,就可以察看到相关文档。
7.2 javax.servlet包
javax.servlet包中所包含的是编写Servlet所需的最基本的类和接口。
它定义的接口有五个:
·RequestDispatcher
·Servlet
·ServletConfig
·ServletContext
·ServletRequest
·ServletResponse
·SingleThreadModel
定义的类有三个:
·GenericServlet
·ServletInputStream
·ServletOutputStream
定义的异常有两个:
·ServletException
·UnavilableException
javax.servlet包中的类的派生关系如图7.3所示(图中箭头表示类所实现的接口,下同,不再说明):
图7.3 javax.servlet类派生结构
以下就对其中定义的接口、类以及例外进行说明。
7.2.1 接口Servlet
所有的Servlet一般情况下都要继承其子类GenericServlet的子类HttpServlet。
这个接口用于开发Servlet。一个Servlet就是一个Java类,它能在一个网络服务中被装载和执行。Web服务器从客户端接受请求,并对请求做出反应,然后把响应的结果返回给客户。例如一个客户需要从一个数据库中获取数据或信息,就可以在服务器端写一个Servlet来完成这一任务。该Servlet接收客户的数据或信息请求,从数据库中得到数据或信息,并可能对取得的结果做出相应的处理,最后返回用户所请求的数据或信息。
所有的Servlet都要实现这个接口。编写一个Servlet程序通常通过生成GenericServlet(GenericServlet已经实现了此接口)类的子类,或者进一步通过生成GenericServlet类的子类HttpServlet的子类来实现。一般在编程时不需要直接实现这个接口,只有在不能够或者不需要从GenericServlet或者HttpServlet继承时,才需要直接实现此接口。比如要把RMI或CORBA当作一个Servlet,就必须实现此接口。
Servlet接口定义了几个方法用于初始化一个Servlet、接收客户的请求、响应客户的请求,以及删除一个Servlet和它所占的资源,这和Applet中的生命周期函数有很多类似的地方。一个Servlet将按照以下步骤被网络服务所调用:
(1)创建并初始化。
(2)处理从客户来的一个或多个服务请求。
(3)删除Servlet,回收所占用的资源,终止Servlet运行。
初始化一个Servlet所作的工作包括许多一次性开销大的处理,比如从一个文件中读出数据或者开始一个辅助线程等等。对于从客户端来的服务请求,Servlet使用接收请求包和发送应答包的工作模式进行处理,其服务质量依赖于下层也就是网络传输层所提供的服务的质量,例如排序、重复、消息的完整性和保密性等等。删除一个Servlet包括撤销它在初始化时所作的所有工作和同步Servlet在内存中的当前持续状态。
除了生命周期函数之外,Servlet接口还提供了一个Servlet获取关于启动信息的方法,以及获取自身信息的方法,这些信息包括作者、版本号和版权信息等。
以下则是Servlet接口的定义:
public abstract interface Servlet{
public void destroy();
/*当所有的线程都从此Servlet的service方法中退出或者超出了预定的时间后,调用此方法,从而释放Servlet所占用的全部资源。*/
public ServletConfig getServletConfig()
/*返回一个ServletConfig配置对象,该对象包含了这个Servlet的所有初始化参数和启动设置。*/
public java.lang.String getServletInfo()
//返回Servlet自身的信息,比如作者、版本号和版权信息等。
public void init(ServletConfig config)
//初始化Servlet并将其调入到服务中,在受到任何请求之前,必须保证已初始化完毕。
public void service(ServletRequest req、ServletResponse res)
//执行一次从客户来的请求。
}
Servlet接口中的service方法是完成请求处理的主要方法,因此应格外给予关注。service方法的两个参数一个代表客户请求,另一个代表对客户的响应。
只有通过service方法才能使Servlet对请求做出响应,当然前提是此Servlet已经通过init方法进行过初始化。当一个Servlet还没有完成初始化时,Servlet引擎将锁定对此方法的请求直到初始化完毕。同样,一旦Servlet引擎通过调用destory方法将此Servlet从服务中移除后,就不能再调用此Servlet的service方法了。
典型情况下,Servlet运行在能够处理并发请求的多线程Servlet引擎之中。所以必须保证对任何共享资源的访问同步,例如网络连接、实例变量等。关于多线程同步可参考Java语言参考中的相关内容。
7.2.2 接口ServletConfig
当Servlet第一次被装载时,为了向其传递服务设置信息,开发者必须实现此接口。在实现此接口时,要为Servlet写一个方法,Servlet使用该方法来获得Servlet的初始化参数和它运行时的环境。
此接口也可以由Servlet来实现(例如GenericServlet就是这样做的,读者可参考相关描述)。当由Servlet实现这一接口时,获取Servlet的设置参数将更加方便。
下面是接口ServletConfig的定义:
public abstract interface ServletConfig{
public java.lang.String getInitParameter(java.lang.String name)
//返回指定名称的Servlet初始化参数,无则返回空值。
public java.util.Enumeration getInitParameterNames()
//返回所有初始化参数的名称,返回值为一枚举对象。
public ServletContext getServletContext()
//返回服务器传递给此Servlet的运行环境,返回值为一个ServletContext对象。
}
配置信息包括初始化参数和一个ServletContext对象。初始化参数是一组“名称/值”对。ServletContext对象则给出服务器的相关信息。
实现接口ServletConfig的实例可参考“JSP内置对象”一章中的“config”对象,“config”对象的原型即实现了此接口。
7.2.3 接口ServletContext
接口ServletContext提供给Servlet一些访问它们所运行环境的方法,并允许记录一些重要的事件,能够由Servlet的编写着来决定记录什么样的数据。此接口一般由网络服务来实现,而让Servlet调用。不同的服务器可能有不同的运行环境。
接口ServletContext中定义了一系列的方法,一个servlet可以使用这些方法来和Servlet引擎进行通讯。例如,获得一个文件的MIME类型、定位其他servlet的在服务器中的位置以及写servlet日志文件等。
Servlet引擎通过向此servlet返回一个ServletContext对象来给出相应的运行环境信息。可以通过ServletConfig.getServletContext()方法来获得ServletContext对象。
如果服务器支持多个或虚拟主机,至少要保证对每个主机来说有唯一的ServletContext对象。Servlet引擎还可以为属于一个主机上同一部分名称地址空间的servlet创建一个ServletContext对象。可以通过定义描述文件来进行群组管理。
ServletContext对象被包含在ServletConfig对象之中,当此servlet初始化时由Web服务器提供。可以通过调用Servlet.getServletConfig() 方法来访问ServletConfig 对象。
接口ServletContext的定义可参考“JSP内置对象”一章中的“application”对象,“application”对象的原型即实现了此接口。
7.2.4 接口ServletRequest
定义了一个对象,以使Servlet引擎可以用它来向servlet提供有关客户请求的信息。
一个ServletRequest对象可以提供数据,包括参数名及参数值、属性和一个输入流。从ServletRequest接口派生的接口能提供进一步的协议相关的数据(例如,HttpServletRequest可以提供HTTP数据)。此接口以及从其派生的接口是Servlet访问这些数据的唯一途径。
一个Servlet请求是一个以MIME为主体的请求,响应也是一个以MIME为主体的响应。MIME主体部分即可以是文本也可以是二进制数据。为文本类型时将包括字符编码方式,可以用getReader方法获得。为二进制数据类型时,则应该使用getInputStream方法来获取输入流。注意多部分传输的请求的MIME主体部分应按照二进制数据处理。
当一个Servlet的service方法时,Servlet就可以调用这个接口的方法,ServletRequest对象作为一个参数传递给service方法。
接口ServletRequest的定义可参考“JSP内置对象”一章中的“request”对象,“request”对象的原型即实现了此接口。
7.2.5 接口ServletResponse
将经过MIME编码的数据从servlet发送到客户端。Servlet引擎将创建一个ServletReponse对象,将其作为参数传递给service方法。
如果要发送二进制数据,则应该使用getOutputStream方法返回的ServletOutputStream对象。同理,为发送文本数据,应使用getWriter方法返回的PrintWriter对象。复杂的情况下,比如希望发送文本和二进制的混合数据,则应该自己构造一个多部分(multipart)的响应,则可以利用ServletOutputStream对象来写出符合结构的响应头部域,然后利用这些头部域来构建主体部分。
如果没有通过setContentType设置响应的MIME类型,Servlet引擎将自动设置默认值。应该在调用getWriter或getOutputStream方法之前调用setContentType方法。
接口ServletResponse的定义可参考“JSP内置对象”一章中的“response”对象,“response”对象的原型即实现了此接口。
7.2.6 接口RequestDispatcher
此接口定义了一个从客户端接收请求,并能够把请求发送到任何服务器资源(比如servlet、HTML文件或者JSP文件)的对象。这个RequestDispatcher对象将由Servlet引擎创建,并把它当作特定路径上的服务器资源的包装。
这个接口是为了包装servlet而定义的,但是Servlet引擎可以为任何类型的资源RequestDispatcher对象。
接口RequestDispatcher的定义如下:
public abstract interface RequestDispatcher{
void forward(ServletRequest request、ServletResponse response)
/*将当前servlet对象中的ServletRequest对象发送给服务器的另一资源(servlet,JSP文件或HTML文件)。*/
void include(ServletRequest request、ServletResponse response)
//包含Web服务器中的另一个资源(servlet,JSP文件或HTML文件)。
}
7.2.7 接口SingleThreadModel
接口SingleThreadModel保证servlet在同一时刻只处理一个请求。此接口中没有定义方法。
如果一个servlet实现了此接口,则可以确定不可能有一个以上的线程同时执行这个servlet的service方法。Servlet引擎通过对此servlet的唯一实例的访问同步来保证这一点;
当然也可以通过维护一个servlet实例池,每次只向空闲servlet发送新的请求来保证。
如果一个servlet实现了此接口,则可以称此servlet是“线程安全”的。然而,此接口并没有处理对共享资源的访问同步,比如静态类成员变量或者超出此servlet范围的类。
7.2.8 类GenericServlet
类GenericServlet定义了一个一般的协议无关的servlet。因此如果要为Web站点写一个处理HTTP协议的servlet,应该从HttpServlet派生。
类GenericServlet实现了Servlet和ServletConfig两个接口。所以当我们要写一个servlet的时候,一般只需从类GenericServlet或它的子类HttpServlet派生就可以了,除非要写的servlet需要其他特殊功能,要从别的类派生。注意,当要从别的类派生时,必须直接实现Servlet接口。
类GenericServlet是我们编写servlet变得容易了很多。它提供了简单的生命周期函数init()和destroy(),并实现了接口ServletConfig中的方法。而且类GenericServlet还实现了接口ServletContext中的log方法,使我们对日志文件的操作也变得容易了。
如果要编写一个普通的servlet,只需要重写其中的service方法即可,因为service方法在类GenericServlet中被声明成了抽象方法,并没有实现。
如果要编写一个Serlvet引擎,则应该重写getServletInfo方法,如果此引擎还要管理超出此servlet范围的资源时,还要写出自己的init和destroy方法。
以下是抽象类GenericServlet的定义:
public abstract class GenericServlet{
GenericServlet()
//什么也不做,因为这是一个抽象类。
void destroy()
//销毁此servlet,释放所有占用的资源。
java.lang.String getInitParameter(java.lang.String name)
//以字符串返回符合特定名称的初始化参数值。
java.util.Enumeration getInitParameterNames()
//返回包含此servlet所有初始化参数名称字符串的一个枚举对象。
ServletConfig getServletConfig()
//获得一个ServletConfig对象,其中包含了此servlet的初始化参数。
ServletContext getServletContext()
//获得一个ServletContext对象,其中包含此servlet所处的Servlet引擎的信息。
java.lang.String getServletInfo()
//获得一个字符串,其中包含此servlet的信息,比如作者、版本和版权声明等。
void init()
//可以不用ServletConfig参数的初始化方法。
void init(ServletConfig config)
//初始化此servlet。
void log(java.lang.String msg)
//向日志文件中写入此servlet的类名及例外消息。
void log(java.lang.String message、java.lang.Throwable t)
//向Servlet日志文件写入一条系统级例外消息。
abstract void service(ServletRequest req、ServletResponse res)
//处理一个来自客户的请求。
}
类GenericServlet对于我们编写servlet非常有用,因此将在随后专门介绍GenericServlet编程。
7.2.9 类ServletInputStream
类ServletInputStream通过以二进制方式读取客户请求来提供一个输入流,其中包含了一个高效的可以一次读取一行数据的readLine方法。针对某些协议,比如HTTP的POST和GET,则可以利用ServletInputStream对象来读取客户发送过来的数据。
可以通过调用ServletRequest.getInputStream()方法来获得对ServletInputStream对象的访问。
类ServletInputStream是由Servlet引擎负责实现的一个抽象类。由这个类派生的子类必须实现其中的java.io.InputStream.read方法。
以下是类ServletInputStream的定义:
public abstract class ServletInputStream{
protected ServletInputStream()
//什么也不做,因为这是一个抽象类。
int readLine(byte[] b、int off、int len)
//一次从输入流中读出一行,注意缓冲区大小的取值。
}
类ServletInputStream从类java.io.InputStream中继承的方法包括:available、close、mark、markSupported、read、reset和skip 。
类ServletInputStream从类java.lang.Object中继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString和wait。
7.2.10 类ServletOutputStream
类ServletOutputStream提供了一个用于向客户发送二进制数据的输出流。可以通过ServletResponse.getOutputStream方法获得一个类ServletOutputStream 的对象。
这是一个抽象类,通常Servlet引擎从这个类派生并且实现。如果要自己生成这个类的派生类,则必须实现其中的java.io.OutputStream.write(int)方法。
以下是类ServletOutputStream的定义:
public abstract class ServletOutputStream{
protected ServletOutputStream()
//什么也不做,因为这是一个抽象类。
void print(boolean b)
void print(char c)
void print(double d)
void print(float f)
void print(int i)
void print(long l)
void print(java.lang.String s)
void println()
void println(boolean b)
void println(char c)
void println(double d)
void println(float f)
void println(int i)
void println(long l)
void println(java.lang.String s)
}
类ServletOutputStream 从类java.io.OutputStream 中继承的方法包括:close、flush和write。
类ServletOutputStream 从类java.lang.Object 中继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString和wait。
7.2.11 异常ServletException
定义了一个由servlet抛出的普通的异常类型。
以下是ServletException的定义:
public class ServletException{
ServletException()
//构造一个新servlet异常对象。
ServletException(java.lang.String message)
//用给出的字符串消息构造一个新servlet异常对象。
ServletException(java.lang.String message、java.lang.Throwable rootCause)
/*当servlet需要抛掷一个异常并且包含影响正常操作的另外一个异常的信息时,用给出的字符串消息构造一个新servlet异常对象。*/
ServletException(java.lang.Throwable rootCause)
//为系统异常使用本地系统异常消息构造一个新的servlet异常对象。
java.lang.Throwable getRootCause()
//获得导致此servlet产生异常的可抛掷的系统异常对象。
}
异常ServletException从类java.lang.Throwable继承的方法包括:fillInStackTrace、getLocalizedMessage、getMessage、printStackTrace、printStackTrace、printStackTrace和toString
异常ServletException 从类java.lang.Object 继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll和wait。
7.2.12 异常UnavailableException
异常UnavailableException定义了一个由servlet抛出的,表示servlet永久或暂时无效的异常类型。
当产生一个servlet永久无效、发生内部错误或者除非某些事件发生否则无法处理请求的状况发生时会导致抛出这种类型的异常。例如,此servlet可能配置错误或者状态被破坏。一个servlet既应该包括记录错误的能力,还要有必需的纠正错误的能力。
由于某些系统方面的原因使servlet无法立刻处理请求将会使此servlet暂时失效,例如一个不可访问的第三方服务器、无效内存或者磁盘存储问题等。系统管理者此使应采取一些纠正动作。
Servlet引擎可以以同样的方式来安全的处理这两种类型的无效异常。然而,有效的处理临时无效更能提高Servlet引擎的鲁棒性。比如,Servlet引擎可以按照servlet所建议的时间暂时锁定请求,这总比拒绝请求直到Servlet引擎重启动要好一些。
以下是异常UnavailableException的定义:
public class UnavailableException{
UnavailableException(int seconds、Servlet servlet、java.lang.String msg)
/*用给出servlet、描述servlet临时无效的消息、时间来构造一个UnavailableException对象。*/
UnavailableException(Servlet servlet、java.lang.String msg)
//用给出servlet和描述servlet临时无效的消息来构造一个UnavailableException对象。
Servlet getServlet()
//返回报告无效的servlet。
int getUnavailableSeconds()
//返回servlet无效的时间。
boolean isPermanent()
//判断此servlet是否是永久性无效。
}
异常UnavailableException 从类javax.servlet.ServletException 继承的方法包括:fillInStackTrace、getLocalizedMessage、getMessage、printStackTrace、printStackTrace、printStackTrace和toString 。
异常UnavailableException 从类java.lang.Object 继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll和wait。
7.3 javax.servlet.http包
javax.servlet.http包中所定义的类和接口是为了支持HTTP协议。
其中定义了五个接口:
·HttpServletRequest
·HttpServletResponse
·HttpSession
·HttpSessionBindingListener
·HttpSessionContext
定义了四个类:
·Cookie
·HttpServlet
·HttpSessionBindinEvent
·HttpUtils
通过javax.servlet.http包所提供的类虽然只有四个,但其中提供的方法足以用来编写一个能够处理客户的HTTP请求的Servlet。
javax.servlet.http包中的类的派生关系如图7.4所示:
图7.4 javax.servlet.http包类派生结构
7.3.1 接口HttpServletRequest
接口HttpServletRequest从接口ServletRequest派生,用来为提交给HTTP servlet的请求对象提供附加的功能方法。
Servlet引擎实现此接口以创建HttpServletRequest对象,HttpServletRequest对象可以将客户信息提交给一个HttpServlet对象的service方法。
接口ServletRequest的定义可参考“JSP内置对象”一章中的“request”对象,“request”对象的原型即实现了此接口。
7.3.2 接口HttpServletResponse
接口HttpServletResponse定义了一个HTTP servlet响应,HTTP servlet指得是能处理HTTP协议规格的servlet,Web服务器必须用此规格来响应客户的请求。此接口允许servlet的service方法访问HTTP包的头部域并向客户端发送数据。Servlet引擎实现了此接口。
接口ServletResponse的定义可参考“JSP内置对象”一章中的“response”对象,“response”对象的原型即实现了此接口。
7.3.3 接口HttpSession
接口HttpSession提供了标示同一用户或用户在Web站点上的轨迹的方法。
Servlet引擎使用此接口以创建session对象,session对象描述了HTTP客户端和HTTP服务器端的一个会话。这个会话将保持一段预定的时间,这段时间内用户可能有多个连接或页请求。一个会话通常对应一个用户,这个用户可能多次访问同一站点。服务器可以通过使用Cookie或者重写URL来维持会话。
通过这个接口,servlet就可以完成以下功能:
1.察看或处理会话信息,比如会话标示符、创建时间及内容等。
2.通过把其他对象绑定到会话对象上,来实现在线购物等功能。
接口HttpSession定义了存储以下数据的方法:
1.标准会话属性,比如会话标示符或内容等。
2.应用程序提供的数据,可以通过这个接口访问,而通过类似目录的接口来存储。
Servlet可以从会话中获取、修改数据,也可以将数据送到会话中。比如下面这段例子:
//获得会话对象,“request”是指HTTP servlet 的request
HttpSession session = request.getSession(true);
//获得一个从会话中读出的整型对象,然后写回会话中,
Integer ival = (Integer) session.getValue("sessiontest.counter");
if (ival==null) ival = new Integer(1);
else ival = new Integer(ival.intValue() + 1);
session.putValue("sessiontest.counter"、ival);
当应用程序向会话中存储一个对象或者从会话中删除一个对象时,会话对象将检测此对象是否实现了HttpSessionBindingListener接口。如果实现了,那么servlet就会通知这个对象它被绑进了会话中或者从会话中删除了。
如何看待一个HTTP会话 session 是从服务器的观点出发的。
如果符合下列情况,服务器将认为这是一个新的会话:
1.客户端还不知道这个会话。
2.此会话还未开始。
3.客户不加入这个会话。比如服务器只支持Cookie而客户却拒绝了服务器发送的Cookie。
当会话是“新”的,isNew()方法将返回true。
servlet必须处理客户不加入会话的情况。下面的这个例子示例了如何通过调用isNew方法来判定会话是否是新的。如果是,服务器通过把客户引导到起始页来要求客户开始一个会话。
HttpSession session = request.getSession(true);
if (session.isNew()) {
response.sendRedirect(welcomeURL);
}
接口HttpSession的定义可参考“JSP内置对象”一章中的“session”对象,“session”对象的原型即实现了此接口。
7.3.4 接口HttpSessionBindingListener
通过接口HttpSessionBindingListener,能够使对象被绑入到一个会话或从会话中移出时获得来自HttpSessionBindingEvent对象的通知。
以下是接口HttpSessionBindingListener的定义:
public abstract interface HttpSessionBindingListener{
void valueBound(HttpSessionBindingEvent event)
//通知对象被绑入一个会话,并标示出此会话。
void valueUnbound(HttpSessionBindingEvent event)
//通知对象被从一个会话中删除,并标示出此会话。
}
7.3.5 类Cookie
类Cookie用以创建一个包含少量信息的cookie,此cookie由一个servlet发送给Web浏览器,并由浏览器保存,当客户随后访问同一Web页面是将送回给服务器。一个cookie的值可以唯一的标识一个客户,因此在会话管理中经常使用。
一个cookie具有一个名称、一个值以及操作属性,比如一段注释、路径、域限定符、最大生存期和版本号等。某些Web浏览器在处理属性时有bug,因此在使用属性时应该多加注意。
servlet通过HttpServletResponse.addCookie 方法来向浏览器发送cookie,cookie被添加到HTTP的响应头之中,每次一个。浏览器应该可以接受发自同一Web服务器的20个cookie。
浏览器也是通过在HTTP请求头中添加域的方法向服务器回送cookie。可以通过HttpServletRequest.getCookies方法从请求中一次获得所有cookie。少数cookie可能具有相同的名称,但具有不同的路径属性。
使用cookie会影响Web页的缓存。HTTP 1.0 不缓存用使用了类Cookie创建的cookie的Web页。类Cookie也不支持HTTP 1.1中定义的缓存控制。
类Cookie具有Version 0(Netscape中定义)和 Version 1(RFC 2109中定义)两种类型。默认情况下,将使用Version 0以保证更好的协同工作性。
以下是类Cookie的定义:
public class Cookie{
Cookie(java.lang.String name、java.lang.String value)
//用给出的名称和值创建一个cookie。
java.lang.Object clone()
//此方法重写了java.lang.Object.clone方法,用以返回由此cookie克隆出来的对象。
java.lang.String getComment()
//获取用于描述使用此cookie的目的的内容,无则返回空值。
java.lang.String getDomain()
//返回为此cookie设置的域名。
int getMaxAge()
//返回此cookie的最长生存时间,以秒为单位。
java.lang.String getName()
//返回此cookie的名称。
java.lang.String getPath()
//返回浏览器回送此cookie的服务器的路径。
boolean getSecure()
//判断浏览器发送cookie时是否使用了安全协议。
java.lang.String getValue()
//返回此cookie的值。
int getVersion()
//返回生成此此cookie所使用的版本号。
void setComment(java.lang.String purpose)
//写入一段描述此cookie用途的注释。
void setDomain(java.lang.String pattern)
//标出此cookie的域标识。
void setMaxAge(int expiry)
//设置此cookie的最大生存时间,以秒为单位。
void setPath(java.lang.String uri)
//设置回送此cookie的路径。
void setSecure(boolean flag)
/*设置安全标志,此标志用来通知浏览器此cookie 只能用特定的安全协议来发送,比如HTTPS 或者SSL。*/
void setValue(java.lang.String newValue)
//创建此cookie后设置此cookie新值。
void setVersion(int v)
//设置创建此cookie所使用的cookie协议版本号。
}
类Cookie从类java.lang.Object继承的方法包括:equals、finalize、getClass、hashCode、notify、notifyAll、toString和wait。
7.3.7 类HttpServlet
Provides an abstract class that you can subclass to create an HTTP servlet、which receives requests from and sends responses to a Web site. When you subclass HttpServlet、you must override at least one method、usually one of these:
类HttpServlet是一个抽象类,可以通过创建从此类派生的子类来创建HTTP servlet,用以接收Web站点的请求或向Web站点发送响应。当从类HttpServlet派生子类时,至少要重写以下方法中的一个:
1.doGet方法,支持HTTP GET 请求。
2.doPost方法,支持HTTP POST请求。
3.doPut方法,支持HTTP PUT 请求。
4.doDelete方法,支持HTTP DELETE请求。
5.init方法和destroy方法,这两个方法要同时改写以控制servlet的生命周期。
6.getServletInfo方法,此方法被servlet用来提供自身的信息。
一般情况下不需要重写service方法。service方法将按照每一个标准的HTTP请求的类型进行分发处理。
同样,也不需要重写doOptions方法和doTrace方法,service方法支持HTTP 1.1标准中的TRACE和OPTIONS请求,自动调用相应的doTrace s方法和doOption方法。
典型情况下,Servlet运行在能够处理并发请求的多线程Servlet引擎之中。所以必须保证对任何共享资源的访问同步,例如网络连接、实例变量等。关于多线程同步可参考Java语言参考中的相关内容。
以下是抽象类HttpServlet的定义:
public abstract class HttpServlet{
HttpServlet()
//什么也不做,因为这是一个抽象类。
protected void doDelete(HttpServletRequest req、HttpServletResponse resp)
//从保护的service方法中接收一个HTTP DELETE请求并处理此请求。
protected void doGet(HttpServletRequest req、HttpServletResponse resp)
//从保护的service方法中接收一个HTTP GET请求并处理此请求。
protected void doOptions(HttpServletRequest req、HttpServletResponse resp)
//从保护的service方法中接收一个HTTP OPTIONS请求并处理此请求。
protected void doPost(HttpServletRequest req、HttpServletResponse resp)
//从保护的service方法中接收一个HTTP POST请求并处理此请求。
protected void doPut(HttpServletRequest req、HttpServletResponse resp)
//从保护的service方法中接收一个HTTP PUT请求并处理此请求。
protected void doTrace(HttpServletRequest req、HttpServletResponse resp)
//从保护的service方法中接收一个HTTP TRACE请求并处理此请求。
protected long getLastModified(HttpServletRequest req)
//返回此HttpServletRequest最后一次修改的时间,是距“01/01/1970 GMT”的毫秒数。
protected void service(HttpServletRequest req、HttpServletResponse resp)
//从公用的service方法中接收标准HTTP请求并将其发送给此类中定义的相应的doxxx方法。
void service(ServletRequest req、ServletResponse res)
//将客户请求发送给保护的service方法。
}
类HttpServlet从类javax.servlet.GenericServlet继承的方法包括:destroy、getInitParameter、getInitParameterNames、getServletConfig、getServletContext、getServletInfo、init和log。
类HttpServlet从类java.lang.Object继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString和wait 。
随后将专门讲解HttpServlet编程。
7.3.8 类HttpSessionBindinEvent
类HttpSessionBindinEvent是一种事件类,当一个实现了HttpSessionBindingListener接口的对象被绑入一个会话或者从一个会话中删除时由会话对象发送给此对象。
会话通过调用HttpSession.putValue方法绑定一个对象,移除一个对象则通过调用HttpSession.removeValue方法。
以下是类HttpSessionBindinEvent的定义:
public class HttpSessionBindingEvent{
HttpSessionBindingEvent(HttpSession session、java.lang.String name)
//利用给出的会话名称和对象名称生成一个事件对象。
java.lang.String getName()
// 返回关联到会话的对象名。
HttpSession getSession()
//返回所关联的会话名称。
}
类HttpSessionBindinEvent 从类java.util.EventObject继承的方法包括:getSource和toString 。
类HttpSessionBindinEvent 从类java.lang.Object继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll和wait。
7.3.9 类HttpUtils
类HttpUtils给出了一套便于编写HTTP servlet的方法集合。
以下是类HttpUtils的定义:
public class HttpUtils{
HttpUtils()
//构造一个空HttpUtils对象。
static java.lang.StringBuffer getRequestURL(HttpServletRequest req)
//使用参数HttpServletRequest对象中的信息来重构客户生成此请求所使用的URL。
static java.util.Hashtable parsePostData(int len、ServletInputStream in)
//解析一个HTML FORM表单的信息,此表单使用HTTP POST方法且定义MIME类型为application/x-www-form-urlencoded,由客户发送到服务器。
static java.util.Hashtable parseQueryString(java.lang.String s)
//解析一个由客户端发送到服务器的查询字符串,并将其构造为一个“关键字/值”对形式的HashTable对象。
}
类HttpSessionBindinEvent 从类java.lang.Object继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString和wait。
7.4 javax.servlet.jsp包
通过前两节的说明和这一节的描述,读者应该能明白JSP服务器的工作流程和原理。
如果不太明白,则应该反复阅读。javax.servlet.jsp包中包括两个接口和四个类,如下:
·HttpJspPage
·JspPage
·JspEngineInfo
·JspFactory
·JspWriter
·PageContext
javax.servlet.jsp包中的类的派生关系如图7.5所示:
图7.5 javax.servlet.jsp包类派生结构
7.4.1 接口HttpJspPage
接口HttpJspPage是一个由JSP处理器所产生的符合HTTP协议的类所必需完成的接口,其中只定义了一个方法_jspService。
以下是接口HttpJspPage的定义:
public abstract interface HttpJspPage{
void _jspService(HttpServletRequest request、HttpServletResponse response)
/*_jspService对应着JSP页的主体部分。此方法由JSP处理器自动定义,而不可能由servlet编写者定义。*/
}
接口HttpJspPage 从接口javax.servlet.jsp.JspPage所继承的方法包括:jspDestroy和jspInit。
接口HttpJspPage 从接口javax.servlet.Servlet所继承的方法包括:destroy、getServletConfig、getServletInfo、init和service。
7.4.2 接口JspPage
接口JspPage是由JSP处理器所产生的类所必需实现的接口(也可以是间接的)。
这个接口通过三个方法来定义一个协议,但只有其中的jspInit()和jspDestroy()方法是这个接口的一部分。另一个特殊的方法_jspService()依赖于特定的协议,因此无法用Java中一般的办法来处理。
实现了这个接口的类有责任在适当的时候调用以上所说的方法,并且调用应该是基于Servlet方法的调用。
jspInit方法和jspDestroy方法可以由JSP编写者定义,但_jspService 方法将由JSP处理器根据JSP页的内容自动产生。
以下是接口JspPage的定义:
public abstract interface JspPage{
void jspDestroy()
//当此JspPage对象将要被销毁时jsp_destroy()将被自动调用。
void jspInit()
//当此JspPage对象被初始化时jsp_init()将被自动调用。
}
接口JspPage从接口javax.servlet.Servlet继承的方法包括:destroy、getServletConfig、getServletInfo、init和service 。
7.4.3 类JspEngineInfo
类JspEngineInfo是用来提供当前JSP引擎信息的抽象类,其定义如下:
public abstract class JspEngineInfo{
JspEngineInfo()
//什么也不做,因为这是一个抽象类。
abstract java.lang.String getImplementationVersion()
//获得版本号,是一个由十进制整数和小数点组成的字符串,比如“2.0.1”。
}
类JspEngineInfo从类java.lang.Object 继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString和wait。
7.4.4 类JspFactory
类JspFactory是一个抽象类,定义了一系列工厂方法。可以供JSP创建用于运行时实现JSP的大量接口及类的实例。
一个合格的JSP引擎实现应该在其初始化时实例化一个此类的子类,并使其全局有效,以供JSP执行类使用。这个实例使用静态方法setDefaultFactory创建并注册。
以下是类JspFactory的定义:
public abstract class JspFactory{
JspFactory()
//什么也不做,因为这是一个抽象类。
static JspFactory getDefaultFactory()
//获得这次执行的默认工厂对象。
abstract JspEngineInfo getEngineInfo()
//获得当前JSP引擎的特定执行信息。
abstract PageContext getPageContext(Servlet servlet、ServletRequest request、
ServletResponse response、java.lang.String errorPageURL、
boolean needsSession、int buffer、boolean autoflush)
/*为发出调用呼叫的servlet和未决的请求及响应对象获取一个当前执行的实例对象,这个实例依赖于javax.servlet.jsp.PageContext抽象类。*/
abstract void releasePageContext(PageContext pc)
//调用此方法以释放早先分配的PageContext对象。
static void setDefaultFactory(JspFactory deflt)
//为此次执行设置缺省的工厂对象。
}
类JspFactory从类java.lang.Object 继承的方法包括:clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString和wait。
7.4.5 类JspWriter
这个类实现了一些类似类java.io.BufferedWriter和类java.io.PrintWriter中存在的一些有用的方法,但不同的是,它会从print方法中抛出java.io.IOException例外。
如果页指令中设置“autoflush="true"”,那么此类中的所有I/O操作将自动处理缓冲区。如果页指令中设置“autoflush="false"”,那么如果缓冲区溢出将产生IOException例外。
接口JspWriter的定义可参考“JSP内置对象”一章中的“out”对象,“out”对象的原型即实现了此接口。
7.4.6 类PageContext
一个类PageContext的实例可以提供对关联到某个JSP页面的命名空间的访问,也可以提供对servlet页属性的访问。
以类PageContext的子类为原型创建的执行时实例,将由JSP实现类通过默认JspFactory对象在其_jspService方法中创建。如下例所示,同时可参考Tomcat目录中work目录下由JSP引擎自动产生的Java源文件:
public class foo implements Servlet {
public void _jspService(HttpServletRequest request,HttpServletResponse response)
throws IOException、ServletException{
JspFactory factory= JspFactory.getDefaultFactory();
PageContext pageContext = factory.getPageContext(this,request,response,
null, // errorPageURL
false, // needsSession
JspWriter.DEFAULT_BUFFER,
true // autoFlush
);
// 初始化默认环境设置
HttpSession session = pageContext.getSession();
JspWriter out= pageContext.getOut();
Object page= this;
try {
// 此处是处理过的JSP的主体
} catch (Exception e) {
out.clear();
pageContext.handlePageException(e);
} finally {
out.close();
factory.releasePageContext(pageContext);
}
}
类PageContext是一个抽象类,是为了提供适当的JSP引擎运行环境信息。可以通过调用JspFactory.getPageContext方法来获得一个PageContext实例,此实例也可通过调用JspFactory.releasePageContext方法释放。
类PageContext为开发者提供了一系列方便的工具,比如:
1.独立的用于管理大量命名空间的API。
2.一系列用于访问公用对象的API。
3.为了输出而获得JspWriter对象的机制。
4.管理页面会话的机制。
5.解析页指示以设置环境的机制。
6.在应用程序中将当前请求导向或包含到其他动态组件的机制。
7.进行错误页处理的机制。
接口PageContext的定义可参考“JSP内置对象”一章中的“pageContext”对象,“pageContext”对象的原型即实现了此接口。
7.6 GenericServlet
一个servlet程序必须要考虑全面,这是因为servlet在服务器上执行,为客户提供服务,因此必须保证很好的并发性。允许servlet并发执行,就要解决servlet中变量和同步访问以及资源共享问题。此外,servlet要把处理结果返回给客户,所以还要考虑响应速度,并且应该保证一定的容错性。这些问题需要读者具有较强的基础之后才能全面的考虑,作为入门性的书籍,本书对此不做更多的讨论。
7.6.1 GenericServlet编程入门
作为GenericServlet编程入门,首先请读者练习一下下面的例子“HelloServlet.java”,代码如下:
“HelloServlet.java”
import java.io.*;
import java.util.*;
//导入servlet包
import javax.servlet.*;
public class HelloServlet extends GenericServlet{
public void init(ServletConfig config)throws ServletException{
super.init(config);
//调用父类的初始化方法;也可以加入自己需要的初始化代码。
}
public void destroy(){
//destroy方法中加入一些做最后清理工作的代码;
}
public String getServletInfo(){
return "This servlet is a simple Servlet's example.";
//返回此servlet的信息 ;
}
public void service(ServletRequest req,ServletResponse res)
throws ServletException,IOException{ //service是最主要的方法,提供服务
//获得服务器当前时间。
Date today=new Date();
//获得响应用户请求的输出流,以反馈执行结果;
ServletOutputStream out=res.getOutputStream();
//通过输出流向客户端写回了一个HTML文件;
out.println("<html><head><title>HelloServlet.java</title></head><body>");
out.println("Hello,this is my first test.+<BR>");
out.println("Today is "+today.toString()+"<BR>");
out.println(getServletInfo()+"<BR>");
}
}
通过这个简单的例子,可以看出编写servlet的一些格式和注意事项:首先要导入servlet包,使用语句“import javax.servlet.*”;其次要实现一些方法,比如init、destory和service;
然后就是在编程中用到的各种Sevlet对象,比如ServletConfig、ServletOutputStream、ServletRequest、ServletResponse以及其他将要用到的对象。获得这些对象有两种方式,一种是由Servlet引擎在调用servlet中的方法时带进来的对象实例,另一种是我们自己通过调用合适的方法获得的。如何获得这些对象对于我们编程非常重要,读者需要熟悉在本章前面几个小节中所介绍的各种类及其方法。
按照上一小节给出的步骤执行,将得到如图7.7所示的结果:
图7.7 执行“HelloServlet”
7.6.2 生命周期函数
在上一小节的例子“HelloServlet.java”中,我们重写了GenenricServlet中的几个方法,包括init、destory、getServletInfo和service。
init方法仅在服务器装载此servlet的时候才自动被调用一次,这与service方法不同:每次客户向服务器发出请求时服务器就会调用service方法。所以在我们修改了源文件并重新编译之后,应该重新启动Servlet引擎,在这里也就是指需要重新执行“servletrunner”。
init只有一个参数,就是ServletConfig对象,init方法的主要工作也就是要把ServletConfig对象保存起来。ServletConfig是有关此次调入的Servlet配置信息的一个对象,这些配置信息是非常重要的,因此当重写init方法是一定要通过调用super.init方法将这个对象保存起来。当init方法没有执行完之前,所有对此servlet的请求都会被阻塞,直到初始化完毕才会被处理。由于初始化只执行一次,所以servlet在效率方面要优于CGI。
与init相对应的是destroy方法,当服务器资源不足,例如内存不足、磁盘空间不够等需要卸载servlet时就会调用此方法,以释放servlet所占用的资源。对于卸载还有一个规定,假如此时servlet正在响应客户的请求,服务器将会等它响应完成以后或者等待一定时间以后才执行卸载,调用destroy方法。
一般情况下是不需要重写init和destroy方法的,只有涉及到公用变量使用等问题时才考虑是否重写。
service方法是类GenericServlet中最重要的方法。每次客户向服务器发出请求时,服务器就会调用service方法以响应客户请求。所以为了对客户请求做出响应,必须重写这个方法,通过加入自己编写的代码来实现对客户的响应。
service方法有两个参数:ServletRequest和ServletResponse。其中SevletRequest对象中保存了客户向服务器所发送的所有信息,比如客户的IP地址、各个参数和内容长度等。对象ServletResponse用来设置如何对用户请求进行响应,其中有三个方法:
1.setContentType(String)可以用来设置客户响应的类型。
2.setContentLength(int)用来设置对客户响应的应答包的长度。
3.getOutputStream()得到用以写入响应数据的输出流。
在例子“HelloServlet.java”中,我们把获得的输出流传递给了ServletOutputStream对象out,通过这个out对象写入响应数据,注意此out对象的类型和JSP的内置对象out不属于同一类型,虽然一般情况下用起来差不多,但在复杂情况下应注意类型差别。
7.6.3 常用对象
这以小节我们仍通过示例来熟悉几种servlet中常见对象的使用,也就是指请求对象、响应对象、配置对象等。同样,学习这部分时可以参考JSP内置对象的使用。
例子“testRequest.java”示例了请求对象的使用。
“testRequest.java”
import java.io.*;
import javax.servlet.*;
public class testRequest extends GenericServlet{
public void service(ServletRequest req,ServletResponse res)
throws ServletException,IOException{
ServletOutputStream out=res.getOutputStream();
out.println("<pre>");
//利用<pre>标记使println的换行有效,这样就不用再加<br>了。
out.println("Information about request Object:");
out.println();
//以下所调用的方法定义在接口ServletRequest中,请察看接口定义。
out.println("Contentlength="+req.getContentLength());
out.println("Contenttype="+req.getContentType());
out.println("Protocol="+req.getProtocol());
out.println("Scheme="+req.getScheme());
out.println("Servername="+req.getServerName());
out.println("Serverport="+req.getServerPort());
out.println("Remote address="+req.getRemoteAddr());
out.println("Remote host="+req.getRemoteHost());
out.println("<pre>");
}
}
由“testRequest.java”编译出来的servlet的执行结果如图7.8所示:
图7.8 “testRequest”执行结果
上面这个例子示例了请求对象的使用,下面再让我们再看一下配置对象,如例子“testConfig.java”:
“testConfig.java”
import java.io.*;
import java.util.*;
import javax.servlet.*;
public class testConfig extends GenericServlet{
//通过变量来保证对象的可见范围为整个servlet,便于使用;
ServletConfig conf;
ServletContext cont;
public void init(ServletConfig config)throws ServletException{
super.init(config);
//编程习惯,最好不要直接使用传递进来的config参数
conf=getServletConfig();
}
public String getServletInfo(){
return "This is a test of Config object.";
}
public void service(ServletRequest req,ServletResponse res)
throws ServletException,IOException{
ServletOutputStream out=res.getOutputStream();
out.println("<pre>");
out.println("Information about request Object:");
out.println();
//使用枚举获得所有参数
Enumeration enum=conf.getInitParameterNames();
if(enum!=null){
out.println("All parameters in request:");
while(enum.hasMoreElements()){
String para=(String)enum.nextElement();
out.println("Parameter "+para+"="+getInitParameter(para));
}
}
out.println("Information about context of Config object:");
cont=conf.getServletContext();
out.println("Mimetype="+cont.getMimeType("testConfig.java"));
out.println("Realpath="+cont.getRealPath("testConfig.java"));
out.println("<pre>");
}
}
“testConfig.java”中需注意的是枚举变量的使用,在获取参数时会经常用到。因为没有什么初始化参数,所以获取参数的一部分执行结果为空。图7.9是“testConfig”的执行结果,如下:
图7.9 “testRequest”执行结果
有关MIME类型的说明请参考附录二。
对于响应对象,就像前两个例子中一样,基本上是用来获取输出流,以输出文本或二进制数据。
7.7 HttpServlet
HttpServlet是从GenericServlet派生出来的,因此它也有init和destroy这两个生命周期函数和service方法。此外,为了支持HTTP协议中的post和get方法,HttpServlet定义了两个重要的方法doPost和doGet。同样,我们首先通过例子来说明它们的用法,逐步介绍HttpServlet编程。
先请看例子“testHttpServlet.java”,如下所示:
“testHttpServlet.java”
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class testHttpServlet extends HttpServlet{
public void doGet(HttpServletRequest req,HttpServletResponse res)
throws ServletException,IOException{
res.setContentType("text/html");
ServletOutputStream out=res.getOutputStream();
out.println("<html>");
out.println("<head><title>testHttpServlet.java</title></head>");
out.println("<body>");
out.println("<h3>Hello,this is a HttpServlet's test!</h3>");
out.println("</body></html>");
}
}
这个例子只是向客户端发送了一个简单的HTML文件,执行结果如图7.10所示:
图7.10 “testHttpServlet”执行结果
这个例子使用了Tomcat服务器,执行结果与前两个例子类似,但使用的是doGet方法。在实际使用中应根据情况重写不同的方法,具体请参考前面小节中有关javax.servlet.http包的介绍。
HttpServlet中的HttpServletRequest对象和HttpServletResponse对象与GenericServlet中的ServleRequest和ServletResponse的作用类似,因为它们分别派生自这两个接口。接下来的例子“SnoopServlet.java”示例了这两个对象所包含的信息,实现了一个反映多方面信息的doGet方法,这个例子是Tomcat自带的。
“SnoopServlet.java”
// $Id: SnoopServlet.java,v 1.3 1999/10/15 21:31:49 duncan Exp $
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.*;
import javax.servlet.http.*;
// @author James Duncan Davidson <duncan@eng.sun.com>
// @author Jason Hunter <jch@eng.sun.com>
public class SnoopServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
PrintWriter out = response.getWriter();
response.setContentType("text/plain");
out.println("Snoop Servlet");
out.println();
out.println("Servlet init parameters:");
Enumeration e = getInitParameterNames();
while (e.hasMoreElements()) {
String key = (String)e.nextElement();
String value = getInitParameter(key);
out.println(" " + key + " = " + value);
}
out.println();
out.println("Context init parameters:");
ServletContext context = getServletContext();
Enumeration enum = context.getInitParameterNames();
while (enum.hasMoreElements()) {
String key = (String)enum.nextElement();
Object value = context.getInitParameter(key);
out.println(" " + key + " = " + value);
}
out.println();
out.println("Context attributes:");
enum = context.getAttributeNames();
while (enum.hasMoreElements()) {
String key = (String)enum.nextElement();
Object value = context.getAttribute(key);
out.println(" " + key + " = " + value);
}
out.println();
out.println("Request attributes:");
e = request.getAttributeNames();
while (e.hasMoreElements()) {
String key = (String)e.nextElement();
Object value = request.getAttribute(key);
out.println(" " + key + " = " + value);
}
out.println();
out.println("Servlet Name: " + getServletName());
out.println("Protocol: " + request.getProtocol());
out.println("Scheme: " + request.getScheme());
out.println("Server Name: " + request.getServerName());
out.println("Server Port: " + request.getServerPort());
out.println("Server Info: " + context.getServerInfo());
out.println("Remote Addr: " + request.getRemoteAddr());
out.println("Remote Host: " + request.getRemoteHost());
out.println("Character Encoding: " + request.getCharacterEncoding());
out.println("Content Length: " + request.getContentLength());
out.println("Content Type: "+ request.getContentType());
out.println("Locale: "+ request.getLocale());
out.println("Default Response Buffer: "+ response.getBufferSize());
out.println();
out.println("Parameter names in this request:");
e = request.getParameterNames();
while (e.hasMoreElements()) {
String key = (String)e.nextElement();
String[] values = request.getParameterValues(key);
out.print(" " + key + " = ");
for(int i = 0; i < values.length; i++) {
out.print(values[i] + " ");
}
out.println();
}
out.println();
out.println("Headers in this request:");
e = request.getHeaderNames();
while (e.hasMoreElements()) {
String key = (String)e.nextElement();
String value = request.getHeader(key);
out.println(" " + key + ": " + value);
}
out.println();
out.println("Cookies in this request:");
Cookie[] cookies = request.getCookies();
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
out.println(" " + cookie.getName() + " = " + cookie.getValue());
}
out.println();
out.println("Request Is Secure: " + request.isSecure());
out.println("Auth Type: " + request.getAuthType());
out.println("HTTP Method: " + request.getMethod());
out.println("Remote User: " + request.getRemoteUser());
out.println("Request URI: " + request.getRequestURI());
out.println("Context Path: " + request.getContextPath());
out.println("Servlet Path: " + request.getServletPath());
out.println("Path Info: " + request.getPathInfo());
out.println("Path Trans: " + request.getPathTranslated());
out.println("Query String: " + request.getQueryString());
out.println();
HttpSession session = request.getSession();
out.println("Requested Session Id: " +request.getRequestedSessionId());
out.println("Current Session Id: " + session.getId());
out.println("Session Created Time: " + session.getCreationTime());
out.println("Session Last Accessed Time: " + session.getLastAccessedTime());
out.println("Session Max Inactive Interval Seconds: " + session.getMaxInactiveInterval());
out.println();
out.println("Session values: ");
Enumeration names = session.getAttributeNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
out.println(" " + name + " = " + session.getAttribute(name));
}
}
}
在浏览器中输入“http://localhost:8080/servlet/SnoopServlet”,则得到的输出结果如表7.1所示:
表7.1 SnoopServlet执行结果
在这个例子中还示例了如何在HttpServlet中使用Cookie及Session,读者可对照本章讲解javax.servlet.http包中相关说明进行分析。
7.8 小结与习题
在前面的例子中我们并未涉及到异常处理,下面简单进行一下介绍。
异常就是指干扰程序正常运行的不正常现象,包括很多种类,例如:数组索引越界、除数为零、文件不存在以及网络故障等等。必须正确的处理这些异常,否则这个程序就是一个不可靠和不正确的程序。
Java中的异常处理机制非常清晰,通过“try—catch—finally”来“试探—捕获—清理”异常。正确的异常处理首先需要明白Java中的异常处理机制,其次要能够确定可能发生异常的位置或时期。在Servlet包中定义了两种异常,分别是ServletException和UnavailableException,ServletException是所有servlet异常的基类,而UnavailableException说明了一个servlet不可到达。
Servlet能够动态的扩充Java服务器的功能,处理来自客户端的请求,并且可以通过有用户协议的接口与客户通讯,消除了客户机/服务器程序的无效性,在概念上和实现上比CGI更加先进,但也要复杂一些。
Servlet开发包为我们开发servlet提供了强大的工具,因此,熟悉Servlet开发包是读者首先应做的事情。进而通过熟悉Servlet开发包来是我们更加深刻的了解Java服务器的工作原理,以开发出更完善更强大的应用程序。
习题:
1.Servlet与JSP之间是什么样的关系?
2.JSP页面的执行过程包括哪些步骤?
3.JSP内置对象和Servlet开发包中哪些类关系密切?是什么关系?
4.简述JSP引擎的工作原理。