2.1 Servlet API
这一节我们主要介绍一下开发Servlet需要用到的主要接口和类,这些接口和类的UML类图如图2-1所示。
图2-1 Servlet API中主要的接口与类的UML类图
2.1.1 Servlet接口
在Java语言中,我们已经了解了Java Applet(Java小应用程序)。它运行在客户端的浏览器中。Java Applet与Java Servlet有以下一些共同点。
·它们都不是独立的应用程序,都没有main()方法。
·它们都不是由用户或程序员直接调用,而是生存在容器中,由容器管理。Applet运行在浏览器中,Servlet运行在Servlet容器中。
·它们都有生命周期,都包含了init()和destroy()方法。
当然,Applet与Servlet也有不同点。
·Applet具有图形界面,运行在客户端的浏览器中。
·Servlet没有图形界面,运行在服务器端的Servlet容器中。
要编写一个Applet,需要从java.applet.Applet类派生一个子类;和Applet类似,要编写一个Servlet,需要实现javax.servlet.Servlet接口,该接口定义了如下5个方法。
public void init(ServletConfig config) throws ServletException
public void service(ServletRequest req, ServletResponse res) throws ServletException, java.io.IOException
public void destroy()
public ServletConfig getServletConfig()
public java.lang.String getServletInfo()
下面介绍一下这5个方法的作用。
·init():在Servlet实例化之后,Servlet容器会调用init()方法,来初始化该对象,主要是为了让Servlet对象在处理客户请求前可以完成一些初始化的工作,例如,建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只能被调用一次。init()方法有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用ServletConfig对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。另外,在Servlet中,还可以通过ServletConfig对象获取描述Servlet运行环境的ServletContext对象,使用该对象,Servlet可以和它的Servlet容器进行通信。
·service():容器调用service()方法来处理客户端的请求。要注意的是,在service()方法被容器调用之前,必须确保init()方法正确完成。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()方法。在service()方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
·destroy():当容器检测到一个Servlet对象应该从服务中被移除的时候,容器会调用该对象的destroy()方法,以便让Servlet对象可以释放它所使用的资源,保存数据到持久存储设备中,例如,将内存中的数据保存到数据库中,关闭数据库的连接等。当需要释放内存或者容器关闭时,容器就会调用Servlet对象的destroy()方法。在Servlet容器调用destroy()方法前,如果还有其他的线程正在service()方法中执行,容器会等待这些线程执行完毕或等待服务器设定的超时值到达。一旦Servlet对象的destroy()方法被调用,容器不会再把其他的请求发送给该对象。如果需要该Servlet再次为客户端服务,容器将会重新产生一个Servlet对象来处理客户端的请求。在destroy()方法调用之后,容器会释放这个Servlet对象,在随后的时间内,该对象会被Java的垃圾收集器所回收。
·getServletConfig():该方法返回容器调用init()方法时传递给Servlet对象的ServletConfig对象,ServletConfig对象包含了Servlet的初始化参数。
·getServletInfo():返回一个String类型的字符串,其中包括了关于Servlet的信息,例如,作者、版本和版权。该方法返回的应该是纯文本字符串,而不是任何类型的标记(HTML、XML等)。
Servlet API包含在J2EE中,如果要查看Servlet API的文档,你需要下载J2EE(从J2EE 1.5开始改名为Java EE 5)SDK,Java EE 5 SDK的下载地址如下:http://java.sun.com/javaee/downloads/?intcmp=1282。
2.1.2 ServletRequest和ServletResponse
Servlet由Servlet容器来管理,当客户请求到来时,容器创建一个ServletRequest对象,封装请求数据,同时创建一个ServletResponse对象,封装响应数据。这两个对象将被容器作为service()方法的参数传递给Servlet,Servlet利用ServletRequest对象获取客户端发来的请求数据,利用ServletResponse对象发送响应数据。
ServletRequest和ServletResponse接口都在javax.servlet包中定义,我们首先看一下ServletRequest接口中的常用方法。
public java.lang.Object getAttribute(java.lang.String name)
返回以name为名字的属性的值。如果该属性不存在,这个方法将返回null。
public java.util.Enumeration getAttributeNames()
返回请求中所有可用的属性的名字。如果在请求中没有属性,这个方法将返回一个空的枚举集合。
public void removeAttribute(java.lang.String name)
移除请求中名字为name的属性。
public void setAttribute(java.lang.String name, java.lang.Object o)
在请求中保存名字为name的属性。如果第二个参数o为null,那么相当于调用removeAttribute(name)。
public java.lang.String getCharacterEncoding()
返回请求正文使用的字符编码的名字。如果请求没有指定字符编码,这个方法将返回null。
public int getContentLength()
以字节为单位,返回请求正文的长度。如果长度不可知,这个方法将返回-1。
public java.lang.String getContentType()
返回请求正文的MIME类型。如果类型不可知,这个方法将返回null。
public ServletInputStream getInputStream()
返回一个输入流,使用该输入流以二进制方式读取请求正文的内容。javax.servlet.ServletInputStream是一个抽象类,继承自java.io.InputStream。
public java.lang.String getLocalAddr()
返回接收到请求的网络接口的IP地址,这个方法是在Servlet 2.4规范中新增的方法。
public java.lang.String getLocalName()
返回接收到请求的IP接口的主机名,这个方法是在Servlet 2.4规范中新增的方法。
public int getLocalPort()
返回接收到请求的网络接口的IP端口号,这个方法是在Servlet 2.4规范中新增的方法。
public java.lang.String getParameter(java.lang.String name)
返回请求中name参数的值。如果name参数有多个值,那么这个方法将返回值列表中的第一个值。如果在请求中没有找到这个参数,这个方法将返回null。
public java.util.Enumeration getParameterNames()
返回请求中包含的所有的参数的名字。如果请求中没有参数,这个方法将返回一个空的枚举集合。
public java.lang.String[] getParameterValues(java.lang.String name)
返回请求中name参数所有的值。如果这个参数在请求中并不存在,这个方法将返回null。
public java.lang.String getProtocol()
返回请求使用的协议的名字和版本,例如:HTTP/1.1。
public java.io.BufferedReader getReader() throws java.io.IOException
返回BufferedReader对象,以字符数据方式读取请求正文。
public java.lang.String getRemoteAddr()
返回发送请求的客户端或者最后一个代理服务器的IP地址。
public java.lang.String getRemoteHost()
返回发送请求的客户端或者最后一个代理服务器的完整限定名。
public int getRemotePort()
返回发送请求的客户端或者最后一个代理服务器的IP源端口,这个方法是在Servlet 2.4规范中新增的方法。
public RequestDispatcher getRequestDispatcher(java.lang.String path)
返回RequestDispatcher对象,作为path所定位的资源的封装。
public java.lang.String getServerName()
返回请求发送到的服务器的主机名。
public int getServerPort()
返回请求发送到的服务器的端口号。
public void setCharacterEncoding (java.lang.String env) throws java.io.Unsupported EncodingException
覆盖在请求正文中所使用的字符编码的名字。
下面我们看一下ServletResponse接口中的常用方法:
public void flushBuffer() throws java.io.IOException
强制把任何在缓存中的内容发送到客户端。
public int getBufferSize()
返回实际用于响应的缓存的大小。如果没有使用缓存,这个方法将返回0。
public java.lang.String getCharacterEncoding()
返回在响应中发送的正文所使用的字符编码(MIME字符集)。
public java.lang.String getContentType()
返回在响应中发送的正文所使用的MIME类型。
public ServletOutputStream getOutputStream() throws java.io.IOException
返回ServletOutputStream对象,用于在响应中写入二进制数据。javax.servlet. ServletOutputStream是一个抽象类,继承自java.io.OutputStream。
public java.io.PrintWriter getWriter() throws java.io.IOException
返回PrintWriter对象,用于发送字符文本到客户端。PrintWriter对象使用getCharacterEncoding()方法返回的字符编码。如果没有指定响应的字符编码方式,默认将使用ISO-8859-1。
public boolean isCommitted()
返回一个布尔值,指示是否已经提交了响应。
public void reset()
清除在缓存中的任何数据,包括状态代码和消息报头。如果响应已经被提交,这个方法将抛出IllegalStateException异常。
public void resetBuffer()
清除在缓存中的响应内容,保留状态代码和消息报头。如果响应已经被提交,这个方法将抛出IllegalStateException异常。
public void setBufferSize(int size)
设置响应正文的缓存大小。Servlet容器将使用一个缓存,其大小至少是请求的尺寸大小。这个方法必须在响应正文被写入之前调用,如果内容已经被写入或者响应对象已经被提交,这个方法将抛出IllegalStateException异常。
public void setCharacterEncoding(java.lang.String charset)
设置发送到客户端的响应的字符编码,例如,UTF-8。
public void setContentLength(int len)
对于HTTP Servlet,在响应中,设置内容正文的长度,这个方法设置HTTP Content-Length实体报头。
public void setContentType(java.lang.String type)
设置要发送到客户端的响应的内容类型,此时响应应该还没有提交。给出的内容类型可以包括字符编码说明,例如:text/html;charset=UTF-8。如果这个方法在getWriter()方法被调用之前调用,那么响应的字符编码将仅从给出的内容类型中设置。这个方法如果在getWriter()方法被调用之后或者在响应被提交之后调用,将不会设置响应的字符编码。在使用HTTP协议的情况中,这个方法设置Content-Type实体报头。
细心的读者可能注意到了,在上面所列举的方法中,有的可能会抛出IllegalStateException异常,然而在函数声明时,却没有声明抛出此异常,这是为什么呢?java.lang.IllegalStateException是java.lang.RuntimeException的子类。我们知道对于RuntimeException及其派生的异常是由Java运行系统自动抛出并自动处理,不需要我们去捕获,所以也就不需要在函数声明时声明抛出异常了。
上面所列的方法,读者不需要将它们都记下来,只要大致看一下,有一个初步的印象就可以了。关键是要理解请求和响应对象能够提供哪些方法,读者可以从客户端与服务器端的交互过程来思考,想想哪些信息是需要获取到的。在Servlet中,用请求对象表示的是什么信息,用响应对象来做什么,哪些信息应该是从请求对象中得到,哪些信息应该是用响应对象来设置。只要理解了交互的过程及请求对象和响应对象所起的作用,当我们需要用到某个方法时,就可以在API文档中进行查找,用的次数多了,这些方法自然也就记住了。
2.1.3 ServletConfig
在javax.servlet包中,定义了ServletConfig接口。Servlet容器使用ServletConfig对象在Servlet初始化期间向它传递配置信息,一个Servlet只有一个ServletConfig对象。在这个接口中,定义了下面四个方法:
public java.lang.String getInitParameter(java.lang.String name)
返回名字为name的初始化参数的值,初始化参数在web.xml配置文件中进行配置。如果参数不存在,这个方法将返回null。
public java.util.Enumeration getInitParameterNames()
返回Servlet所有初始化参数的名字的枚举集合。如果Servlet没有初始化参数,这个方法将返回一个空的枚举集合。
public ServletContext getServletContext()
返回Servlet上下文对象的引用,关于ServletContext的使用,请参见第2.5节。
public java.lang.String getServletName()
返回Servlet实例的名字。这个名字是在Web应用程序的部署描述符中指定的。如果是一个没有注册的Servlet实例,这个方法返回的将是Servlet的类名。
2.1.4 一个简单的Servlet
这一节我们编写一个最简单的Servlet,其功能就是向客户端输出一个字符串“Hello World”。实例的开发主要有下列步骤。
开发步骤
Step1:编写HelloWorldServlet类
编写一个Servlet,实际上就是编写一个实现了javax.servlet.Servlet接口的类。我们首先在%CATALINA_HOME%\webapps目录下新建一个子目录ch02,然后用记事本或者UltraEdit等文本编辑工具编写HelloWorldServlet.java源文件,将编写好的HelloWorldServlet. java源文件放到%CATALINA_HOME%\webapps\ch02\src目录下(读者也可以自行选择存放源代码的目录)。完整的源代码如例2-1所示。
例2-1 HelloWorldServlet.java
package org.sunxin.ch02.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class HelloWorldServlet implements Servlet { private ServletConfig config; public void destroy(){} public ServletConfig getServletConfig() { return config; } /** * 该方法很少使用,因此返回null即可 */ public String getServletInfo() { return null; } /** * ServletConfig对象由容器构造。容器在调用init()方法时,将其作为参数传给Servlet */ public void init(ServletConfig config) throws ServletException { this.config = config; } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { //得到PrintWriter对象。Servlet使用输出流来产生响应 PrintWriter out=res.getWriter(); //向客户端发送字符数据 out.println("Hello World"); //关闭输出流 out.close(); } }
在Servlet中,主要的方法是service(),当客户端请求到来时,Servlet容器将调用Servlet实例的service()方法对请求进行处理。我们在service()方法中,首先通过ServletResponse类中的getWriter()方法调用得到一个PrintWriter类型的输出流对象out,然后调用out对象的println()方法向客户端发送字符串“Hello World”,最后关闭out对象。
Servlet容器调用Servlet实例对请求的处理过程如图2-2所示。
图2-2 Servlet容器调用Servlet实例对请求进行处理的全过程
Step2:编译HelloWorldServlet.java
打开命令提示符,转到HelloWorldServlet.java所在的目录%CATALINA_HOME%\webapps\ch02\src下,然后执行:
javac -d . HelloWorldServlet.java
大多数情况下,你会看到如图2-3所示的画面。
图2-3 编译HelloWorldServlet.java的出错信息
产生这些错误的原因是Java编译器没有找到javax.servlet包中的类。要解决这个问题,我们需要让Java编译器知道Servlet API库所在的位置。Tomcat在其发行版中已经包含了Servlet API库,是以JAR文件的形式提供的,这个JAR文件的完整路径名是:
%CATALINA_HOME%\lib\servlet-api.jar
我们只需要在系统的CLASSPATH环境变量下添加这个JAR文件的路径名就可以了。
设置CLASSPATH环境变量的方法和第1章设置JAVA_HOME环境变量的方法是一样的,在笔者的机器上CLASSPATH环境变量的配置如下:
CLASSPATH=.;D:\OpenSource\apache-tomcat-6.0.16\lib\servlet-api.jar
关闭刚才打开的命令提示符窗口,重新打开一个新的命令提示符窗口,进入HelloWorldServlet.java所在的目录,再次执行:
javac -d . HelloWorldServlet.java
生成org\sunxin\ch02\servlet目录结构,以及在servlet子目录中的HelloWorldServlet.class文件。
如果你已经安装了J2EE SDK,那么在安装目录的lib子目录下有一个javaee.jar文件(J2EE 1.4以及之前的版本是j2ee.jar文件),其中包含了Servlet API库。你可以在CLASSPATH环境变量下添加javaee.jar所在的路径名,就不需要再配置Tomcat中的servlet-api.jar了。配置了javaee.jar后,你还可以开发其他的J2EE应用。
Step3:部署HelloWorldServlet
Servlet是Web应用程序中的一个组件。一个Web应用程序是由一组Servlet、HTML页面、类,以及其他的资源组成的运行在Web服务器上的完整的应用程序,以一种结构化的有层次的目录形式存在。组成Web应用程序的这些资源文件要部署在相应的目录层次中,根目录代表了整个Web应用程序的根。我们通常是将Web应用程序的目录放到%CATALINA_HOME%\webapps目录下,在webapps目录下的每一个子目录都是一个独立的Web应用程序,子目录的名字就是Web应用程序的名字,也称为Web应用程序的上下文根。用户通过Web应用程序的上下文根来访问Web应用程序中的资源,如图2-4所示。
图2-4 多个Web应用程序和上下文根
如果你要新建一个Web应用程序,可以在webapps目录下先建一个目录,在这个例子中,我们所建的目录是ch02,作为第一个Web应用程序的上下文根。Java开发的Web应用程序需要遵照一定的目录层次结构,在Servlet规范中定义了Web应用程序的目录层次结构,如图2-5所示。
图2-5 Web应用程序的目录层次结构
Web应用程序的目录层次结构如表2-1所示。
表2-1 Web应用程序的目录层次结构
从表2-1中可以看到,WEB-INF目录下的classes和lib目录都可以存放Java的类文件,在Servlet容器运行时,Web应用程序的类加载器将首先加载classes目录下的,其次才是lib目录下的类。如果这两个目录下存在同名的类,起作用的将是classes目录下的类。
在表2-1中,我们还可以看到一个特殊的目录WEB-INF,注意在书写时不要写错,所有字母都要大写。说这个目录特殊,是因为这个目录并不属于Web应用程序可以访问的上下文路径的一部分,对客户端来说,这个目录是不可见的。如果你将index.html文件放到WEB-INF目录下,对于客户端是无法通过下面的方式访问到这个文件的:
http://localhost:8080/ch02/WEB-INF/index.html
不过,WEB-INF目录下的内容对于Servlet代码是可见的,在Servlet代码中可以通过调用ServletContext对象中的getResource()或者getResourceAsStream()方法来访问WEB-INF目录下的资源,也可以使用RequestDispatcher调用(参见第2.6节)将WEB-INF目录下的内容呈现给客户端。
如果我们想要在Servlet代码中访问保存在文件中的配置信息,而又不希望这些配置信息被客户端访问到,就可以把这个文件放到WEB-INF目录下。
在%CATALINA_HOME%\webapps\ch02目录下新建一个目录WEB-INF,进入WEB-INF目录,新建一个classes目录,整个目录结构是:
%CATALINA_HOME%\webapps\ch02\WEB-INF\classes
将编译生成的HelloWorldServlet.class文件连同所在的包一起放到WEB-INF\classes目录下。
接下来,我们需要部署这个Servlet,Web应用程序的配置和部署是通过web.xml文件来完成的。web.xml文件被称为Web应用程序的部署描述符,它可以包含如下的配置和部署信息:
·ServletContext的初始化参数
·Session的配置
·Servlet/JSP的定义和映射
·应用程序生命周期监听器类
·过滤器定义和过滤器映射
·MIME类型映射
·欢迎文件列表
·错误页面
·语言环境和编码映射
·声明式安全配置
·JSP配置
我们所编写的web.xml文件必须是格式良好的XML。用记事本或者UltraEdit等文本编辑工具编写web.xml文件,内容如例2-2所示。
例2-2 web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <servlet> <servlet-name>HelloWorldServlet</servlet-name> <servlet-class> org.sunxin.ch02.servlet.HelloWorldServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldServlet</servlet-name> <url-pattern>/helloworld</url-pattern> </servlet-mapping> </web-app>
第一行是XML声明,接下来在根元素<web-app>上声明了使用的XML Schema的版本。这段代码是固定的,你无须记忆它,只要知道复制/粘贴就可以了。
注意代码中以粗体显示的部分,这部分代码使用了<servlet>和<servlet-mapping>元素,以及它们的子元素来部署HelloWorldServlet。在web.xml文件中,可以包含多个<servlet>和<servlet-mapping>元素,用于部署多个Servlet。
<servlet>元素用于声明Servlet,<servlet-name>子元素用于指定Servlet的名字,在同一个Web应用程序中,每一个Servlet的名字必须是唯一的,该元素的内容不能为空。<servlet-class>子元素用于指定Servlet类的完整限定名(如果有包名,要同时给出包名)。
<servlet-mapping>元素用于在Servlet和URL样式之间定义一个映射。它的子元素<servlet-name>指定的Servlet名字必须和<servlet>元素中的子元素<servlet-name>给出的名字相同。<url-pattern>子元素用于指定对应于Servlet的URL路径,该路径是相对于Web应用程序上下文根的路径。
经过这样的配置之后,我们可以在浏览器的地址栏中输入http://localhost:8080/ch02/helloworld来访问HelloWorldServlet。当Servlet容器接收到/ch02/helloworld的请求后,就会将发送给ch02 Web应用程序的Context(参见第1.6节),ch02 Web应用程序的Context首先移除该Web应用程序上下文路径的前缀/ch02,然后将剩余部分与web.xml文件中配置的<url-pattern>元素的内容相比较,找到对应的Servlet名字为HelloWorldServlet,再根据这个名字找到HelloWorldServlet类,进而实例化这个类,对请求进行处理。
将编写好的web.xml文件保存到%CATALINA_HOME%\webapps\ch02\WEB-INF目录下。读者也可以将%CATALINA_HOME%\webapps\ROOT\WEB-INF目录下的web.xml复制一份,存放到%CATALINA_HOME%\webapps\ch02\WEB-INF目录下,这个文件的内容如下:
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>Welcome to Tomcat</display-name> <description> Welcome to Tomcat </description> </web-app>
然后编辑这个文件,添加HelloWorldServlet的配置,如下所示:
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>Welcome to Tomcat</display-name> <description> Welcome to Tomcat </description> <servlet> <servlet-name>HelloWorldServlet</servlet-name> <servlet-class>org.sunxin.ch02.servlet.HelloWorldServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldServlet</servlet-name> <url-pattern>/helloworld</url-pattern> </servlet-mapping> </web-app>
这个文件中其他元素的作用请参看附录D。
%CATALINA_HOME%\webapps\ROOT目录是Tomcat默认的Web应用程序的起始路径,当我们输入http://localhost:8080/时,访问的就是该目录下的Web应用程序资源。如果你将本例的Servlet部署在该目录下,访问时只需输入http://localhost:8080/helloworld即可。
Step4:访问HelloWorldServlet
当部署好Servlet之后,对客户端来说,访问Servlet和访问静态页面没有什么区别。为了让读者对Servlet的访问有一个较好的感性认识,在这里我们通过两种方式来访问SimpleHello。
第一种方式,我们通过telnet工具来访问本例中的Servlet,具体操作步骤,读者可以参看附录B。
首先确保Tomcat服务器正常启动了。打开命令提示符,输入
telnet localhost 8080
回车后,输入
GET /ch02/helloworld HTTP/1.1 Host: localhost
注意书写与空格。然后连续两个回车,你将看到如图2-6所示的画面。
图2-6 使用telnet工具与Servlet交互的信息
第二种方式,通过浏览器访问Servlet,打开IE,在地址栏中输入:
http://localhost:8080/ch02/helloworld
注意在helloworld后面不要加斜杠(/),然后回车,你将看到如图2-7所示的画面。
图2-7 使用IE访问HelloWorldServlet
在访问Servlet和JSP的时候,Servlet的名字和JSP的文件名都是区分大小写的,对于本例来说,如果你输入的是HelloWorld,那么Tomcat服务器会给出404的错误代码,提示“The requested resource (/ch02/HelloWorld) is not available”。
Web应用程序的开发分为设计开发与配置部署两个阶段。通过部署,实现了组件与组件之间的松耦合,降低了Web应用程序维护的难度。在本例中,为Servlet指定了一个名字和URL映射,其他的组件或页面可以使用URL来调用这个Servlet,一旦Servlet发生了改动,例如,整个类被替换、重新命名等,只需修改web.xml文件中<servlet-class>元素的内容,在设计开发阶段确定的程序结构与代码不需要做任何的改动,降低了程序维护的难度。当然,事物都有两面性,在享受好处的同时,也需要我们花费额外的时间和精力去了解和掌握部署过程。
2.1.5 GenericServlet
在第2.3节中,我们是通过实现Servlet接口来编写的Servlet类,这需要实现Servlet接口中定义的5个方法。为了简化Servlet的编写,在javax.servlet包中提供了一个抽象的类GenericServlet,它给出了除service()方法外的其他4个方法的简单实现。GenericServlet类定义了一个通用的、不依赖于具体协议的Servlet,它实现了Servlet接口和ServletConfig接口。
public abstract class GenericServlet extends java.lang.Object implements Servlet, ServletConfig, java.io.Serializable
如果我们要编写一个通用的Servlet,只需要从GenericServlet类继承,并实现其中的抽象方法service()。
在GenericServlet类中,定义了两个重载的init()方法:
public void init(ServletConfig config) throws ServletException
public void init() throws ServletException
第一个init()方法是Servlet接口中init()方法的实现。在这个方法中,首先将ServletConfig对象保存在一个transient实例变量中,然后调用第二个不带参数的init()方法。
通常我们在编写继承自GenericServlet的Servlet类时,只需要重写第二个不带参数的init()方法就可以了。如果覆盖了第一个init()方法,那么应该在子类的该方法中,包含一句super.init(config)代码的调用。
在GenericServlet类中还定义了下列的方法。
public java.lang.String getInitParameter(java.lang.String name)
返回名字为name的初始化参数的值,初始化参数在web.xml配置文件中进行配置。如果参数不存在,这个方法将返回null。
注意,这个方法只是为了方便而给出的,它实际上是通过调用ServletConfig对象的getInitParameter()方法来得到初始化参数的。
public java.util.Enumeration getInitParameterNames()
返回Servlet所有初始化参数的名字的枚举集合。如果Servlet没有初始化参数,这个方法将返回一个空的枚举集合。
注意,这个方法只是为了方便而给出的,它实际上是通过调用ServletConfig对象的getInitParameterNames()方法来得到所有的初始化参数的名字。
public ServletContext getServletContext()
返回Servlet上下文对象的引用,关于ServletContext的使用,请参见第2.5节。
注意,这个方法只是为了方便而给出的,它实际上是通过调用ServletConfig对象的getServletContext()方法来得到的Servlet上下文对象的引用。
2.1.6 HttpServlet
在绝大多数的网络应用中,都是客户端(浏览器)通过HTTP协议去访问服务器端的资源,而我们所编写的Servlet也主要是应用于HTTP协议的请求和响应。为了快速开发应用于HTTP协议的Servlet类,Sun公司在javax.servlet.http包中给我们提供了一个抽象的类HttpServlet,它继承自GenericServlet类,用于创建适合Web站点的HTTP Servlet。
public abstract class HttpServlet extends GenericServlet implements java.io.Serializable
在HttpServlet类中提供了两个重载的service()方法:
public void service(ServletRequest req, ServletResponse res) throws ServletException, java.io.IOException
protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException
第一个service()方法是GenericServlet类中service()方法的实现。在这个方法中,首先将req和res对象转换为HttpServletRequest(继承自ServletRequest接口)和HttpServletResponse(继承自ServletResponse接口)类型,然后调用第二个service方法,对客户请求进行处理。
针对HTTP1.1中定义的7种请求方法GET、POST、HEAD、PUT、DELETE、TRACE和OPTIONS,HttpServlet分别提供了7个处理方法:
protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException
protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException
protected void doHead (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException
protected void doPut (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException
protected void doDelete (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException
protected void doTrace (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException
protected void doOptions (HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException
这7个方法的参数类型及异常抛出类型与HttpServlet类中的第二个重载的service()方法是一致的。当容器接收到一个针对HttpServlet对象的请求时,调用该对象中的方法顺序如下:
① 调用公共的(public)service()方法。
② 在公共的service()方法中,首先将参数类型转换为HttpServletRequest和HttpServletResponse,然后调用保护的(protected)service()方法,将转换后的HttpServletRequest对象和HttpServletResponse对象作为参数传递进去。
③ 在保护的service()方法中,首先调用HttpServletRequest对象的getMethod()方法,获取HTTP请求方法的名字,然后根据请求方法的类型,调用相应的doXxx ()方法。
因此,我们在编写HttpServlet的派生类时,通常不需要去覆盖service()方法,而只需重写相应的doXXX()方法。
HttpServlet类对TRACE和OPTIONS方法做了适当的实现,因此我们不需要去覆盖doTrace()和doOptions()方法。而对于其他的5个请求方法,HttpServlet类提供的实现都是返回HTTP错误,对于HTTP 1.0的客户端请求,这些方法返回状态代码为400的HTTP错误,表示客户端发送的请求在语法上是错误的。而对于HTTP 1.1的客户端请求,这些方法返回状态代码为405的HTTP错误,表示对于指定资源的请求方法不被允许。这些方法都是使用javax.servlet.ServletRequest接口中的getProtocol()方法来确定协议的。
HttpServlet虽然是抽象类,但在这个类中没有抽象的方法,其中所有的方法都是已经实现的。只是在这个类中对客户请求进行处理的方法,没有真正的实现,当然也不可能真正实现,因为对客户请求如何进行处理,需要根据实际的应用来决定。我们在编写HTTP Servlet的时候,根据应用的需要,重写其中的对客户请求进行处理的方法即可。
2.1.7 HttpServletRequest和HttpServletResponse
在javax.servlet.http包中,定义了HttpServletRequest和HttpServletResponse这两个接口。这两个接口分别继承自javax.servlet.ServletRequest和javax.servlet.ServletResponse接口。在HttpServletRequest接口中新增的常用方法如下:
public java.lang.String getContextPath()
返回请求URI中表示请求上下文的部分,上下文路径是请求URI的开始部分。上下文路径总是以斜杠(/)开头,但结束没有斜杠(/)。在默认(根)上下文中,这个方法返回空字符串""。例如,请求URI为“/sample/test”,调用该方法返回路径为“/sample”。
public Cookie[] getCookies()
返回客户端在此次请求中发送的所有Cookie对象。
public java.lang.String getHeader(java.lang.String name)
返回名字为name的请求报头的值。如果请求中没有包含指定名字的报头,这个方法返回null。
public java.util.Enumeration getHeaderNames()
返回此次请求中包含的所有报头名字的枚举集合。
public java.util.Enumeration getHeaders(java.lang.String name)
返回名字为name的请求报头所有的值的枚举集合。
public java.lang.String getMethod()
返回此次请求所使用的HTTP方法的名字,例如,GET、POST或PUT。
public java.lang.String getPathInfo()
返回与客户端发送的请求URL相联系的额外的路径信息。额外的路径信息是跟在Servlet的路径之后、查询字符串之前的路径,并以斜杠(/)字符开始。例如,假定在web.xml文件中MyServlet类映射的URL是:/myservlet/*,用户请求的URL是:http://localhost:8080/ch02/myservlet/test,当我们在HttpServletRequest对象上调用getPathInfo()时,该方法将返回/test。如果没有额外的路径信息,getPathInfo()方法将返回null。
public java.lang.String getPathTranslated()
将额外的路径信息转换为真实的路径。例如,在上面的例子中假定ch02 Web应用程序位于D:\OpenSource\apache-tomcat-6.0.16\webapps\ch02目录,当用户请求http://localhost:8080/ch02/myservlet/test时,在请求对象上调用getPathTranslated()方法将返回D:\OpenSource\apache-tomcat-6.0.16\webapps\ch02\test。
public java.lang.String getQueryString()
返回请求URL中在路径后的查询字符串。如果在URL中没有查询字符串,该方法返回null。例如,有如下的请求URL:
http://localhost:8080/ch02/logon.jsp?action=logon
调用getQueryString()方法将返回action=logon。
public java.lang.String getRequestURI()
返回请求URL中从主机名到查询字符串之间的部分。例如:
public java.lang.StringBuffer getRequestURL()
重新构造客户端用于发起请求的URL。返回的URL包括了协议、服务器的名字、端口号和服务器的路径,但是不包括查询字符串参数。
要注意的是,如果请求使用RequestDispatcher.forward(ServletRequest, ServletResponse)方法被转发到另一个Servlet中,那么你在这个Servlet中调用getRequestURL(),得到的将是获取RequestDispatcher对象时使用的URL,而不是原始的请求URL。
public java.lang.String getServletPath()
返回请求URI中调用Servlet的部分。这部分的路径以斜杠(/)开始,包括了Servlet的名字或者路径,但是不包括额外的路径信息和查询字符串。例如,假定在web.xml文件中MyServlet类映射的URL是:/myservlet/*,用户请求的URL是:http://localhost:8080/ch02/myservlet/test,当我们在HttpServletRequest对象上调用getServletPath ()时,该方法将返回/myservlet。如果用于处理请求的Servlet与URL样式“/*”相匹配,那么这个方法将返回空字符串("")。
public HttpSession getSession()
返回和此次请求相关联的Session,如果没有给客户端分配Session,则创建一个新的Session。
public HttpSession getSession(boolean create)
返回和此次请求相关联的Session,如果没有给客户端分配Session,而create参数为true,则创建一个新的Session。如果create参数为false,而此次请求没有一个有效的HttpSession,则返回null。
在HttpServletResponse接口中,新增的常用方法如下:
public void addCookie(Cookie cookie)
增加一个Cookie到响应中。这个方法可以被多次调用,用于设置多个Cookie。
public void addHeader(java.lang.String name, java.lang.String value)
用给出的name和value,增加一个响应报头到响应中。
public boolean containsHeader(java.lang.String name)
判断以name为名字的响应报头是否已经设置。
public java.lang.String encodeRedirectURL(java.lang.String url)
使用Session ID对用于重定向的url进行编码,以便用于sendRedirect()方法中。如果该url不需要编码,则返回未改变的url。(关于这个方法的使用,请参见第5章)
public java.lang.String encodeURL(java.lang.String url)
使用Session ID对指定的url进行编码。如果该url不需要编码,则返回未改变的url。(关于这个方法的使用,请参见第5章)
public void sendError(int sc) throws java.io.IOException
使用参数sc表示的状态代码发送一个错误响应到客户端,同时清除缓存。如果响应已经被提交,这个方法将抛出IllegalStateException异常。
public void sendError(int sc, java.lang.String msg) throws java.io.IOException
使用指定的状态代码发送一个错误响应到客户端。服务器默认会创建一个包含了指定消息的服务器端错误页面作为响应,设置内容类型为“text/html”。如果Web应用程序已经声明了对应于指定状态代码的错误页面,则服务器会将这个页面发送给客户端,而不理会参数msg指定的错误消息。如果响应已经被提交,这个方法将抛出IllegalStateException异常。
public void sendRedirect(java.lang.String location) throws java.io.IOException
发送一个临时的重定向响应到客户端,让客户端访问新的URL。如果指定的位置是相对URL,Servlet容器在发送响应到客户端之前,必须将相对URL转换为绝对URL。如果响应已经被提交,这个方法将抛出IllegalStateException异常。
public void setHeader(java.lang.String name, java.lang.String value)
用给出的name和value,设置一个响应报头。如果这个报头已经被设置,新的值将覆盖先前的值。
public void setStatus(int sc)
为响应设置状态代码。
此外,在HttpServletResponse接口中,还定义了一组整型的静态常量,用于表示HTTP错误代码,这些错误代码对应于HTTP/1.1中的错误代码。关于这些错误代码常量,请参看HttpServletResponse接口的API文档。
要想更好地理解HttpServletRequest和HttpServletResponse的使用,应该结合HTTP协议来看,彼此对照。HTTP协议的介绍参见附录B。