Java Servlet是运行在带有支持Java Servlet规范的解释器的web服务器上的Java类。
Servlet可以使用javax.servlet和javax.servlet.http包创建,它是Java企业版的标准组成部分,Java企业版是支持大型开发项目的Java类库的扩展版本。
这些类实现Java Servlet和JSP规范。在编写本书时,Servlet的最新版本是4.0,JSP的最新版本是2.3。
在javax.servlet包中定义了Servlet接口及相关的通用接口和类;在javax.servlet.http包中主要定义了与HTTP协议相关的HttpServlet类、HttpServletRequest、HttpServletResponse接口。图1.28显示了Servlet API中的主要接口与类的类框图。
Servlet API中的主要接口与类
Servlet接口
Servlet API的核心是javax.servlet.Servlet接口,所有的Servlet类都必须实现这一接口。在Servlet接口中定义了5个方法。其中有3个方法都由Servlet容器来调用,容器会在Servlet的生命周期的不同阶段调用特定的方法。
● )init(ServletConfig config)方法:负责初始化Servlet对象。容器在创建好Servlet对象后,就会调用该方法。
● service(ServletRequest request,ServletResponse response)方法:负责响应客户端的请求,为客户提供相应服务。当容器接收到客户端要求访问特定Servlet对象的请求时,就会调用该Servlet对象的service()方法。
● destroy()方法:负责释放Servlet对象占用的资源。当Servlet对象结束生命周期时,容器会调用此方法。
Servlet接口还定义了以下两个返回Servlet的相关信息的方法。JavaWeb应用中的程序代码可以访问Servlet的这两个方法,从而获得Servlet的配置信息及其他相关信息。
● getServletConfig():返回一个ServletConfig对象,在该对象中包含了Servlet的初始化参数信息。
● getServletInfo():返回一个字符串,在该字符串中包含了Servlet的创建者、版本和版权等信息。
在Servlet API中,javax.servlet.GenericServlet抽象类实现了Servlet接口,而javax.servlet.http.HttpServlet抽象类是GenericServlet类的子类。当开发自己的Servlet类时,可以选择扩展GenericServlet类或者HttpServlet类。图1.29显示了Servlet接口及其实现类的类框图。
Servlet接口及其实现类
GenericServlet抽象类
GenericServlet抽象类为Servlet接口提供了通用实现,它与任何网络应用层协议无关。GenericServlet类除了实现Servlet接口,还实现了ServletConfig接口和Serializable接口。下面是GenericServlet类的源代码。
从GenericServlet类的源代码可以看出,GenericServlet类实现了Servlet接口中的init(ServletConfig config)初始化方法。GenericServlet类有一个ServletConfig类型的私有实例变量config,当Servlet容器调用GenericServlet的init(ServletConfig config)方法时,该方法使得私有实例变量config引用有容器传入的ServletConfig对象,即使得GenericServlet对象与一个ServletConfig对象关联。
GenericServlet类还定义了一个不带参数的init()方法,init(ServletConfig config)方法会调用此方法。对于GenericServlet类的子类,如果希望覆盖父类的初始化行为,有以下两种方法:
● 覆盖父类的不带参数的init()方法。
public void init(){
//子类具体的初始化行为
}
● 覆盖父类的带参数的init(ServletConfig config)方法。如果希望当前Servlet对象与ServletConfig对象关联,应该先在该方法中调用super.init(config)方法。
public void init(ServletConfig config){
super.init(config);
//子类具体的初始化行为
}
GenericServlet类没有实现Servlet接口中的service()方法,service()方法是GenericServlet类中唯一的抽象方法,genericServlet类的具体子类必须实现该方法,从而为特定的客户请求提供具体的服务。
GenericServlet类尽管实现了Servlet接口中的destroy()方法,但实际上什么也没做,GenericServlet类的具体子类可以覆盖该方法,从而为待销毁的当前Servlet对象释放所占用的各种资源(例如关闭文件输入流和输出流,关闭与数据库的连接等)。
此外,GenericServlet类实现了ServletConfig接口中的所有方法。因此,GenericServlet类的子类可以直接调用在ServletConfig接口中定义的getServletConfig()、getInitParameter()和getInitParameter()等方法。
HttpServlet抽象类
HttpServlet类是GenericServlet类的子类。HttpServlet类为Servlet接口提供了与HTTP协议相关的通用实现,也就是说,HttpServlet对象适合运行在与客户端采用HTTP协议通信的Servlet容器或者Web服务器中。在开发JavaWeb应用时,自定义的Servlet类一般都扩展HttpServlet类。
HTTP协议把客户端请求分为GET、POST、PUT、和DELETE等多种方式。HtppServlet类针对每一种请求方式都提供了相应的服务方法,如doGet()、doPost()、doPut()和doDelete()等方法。下面提供了HttpServlet类的源代码。
从上面的源代码可以看出,HttpServlet类实现了Servlet接口中的service(ServletRequest req,ServletResponse res)方法,该方法实际上调用的是它的重载方法:
service(HttpServletRequest req,HttpServletResponse res);
在以上重载service()方法中,首先调用HttpServletRequest类型的req参数的getMethod()方法,从而获得客户端的请求方式,然后依据该请求方式调用匹配的服务方法。如果为GET方式,则调用doGet()方法;如果为POST方式,则调用doPost()方法,以此类推。
HttpServlet类为所有针对特定请求方式的doXXX()方法提供了默认的实现,在HttpServlet类的默认实现中,doGet()、doPost()、doPut()和doDelete()方法都会向客户端返回一个错误:
如果客户端与服务器之间采用HTTP1.1协议通讯,那么返回的错误为HttpServletResponse. SC_METHOD_NOT_ALLOWED(对应HTTP协议中相应状态码为405的错误)。
如果客户端与服务器之间不是采用HTTP1.1协议通讯,那么返回的错误为HttpServletResponse.SC_BAD_REQUEST(对应HTTP协议中相应状态码为400的错误 )。
对于HttpServlet类的具体子类,一般会针对客户端的特定请求方式,来覆盖HttpServlet父类中的相应doXXX()方法。为了使doXXX()方法能被Servlet容器访问,应该把访问权限设置为public。嘉定HelloServlet类是HttpServlet类的子类,如果客户端只会按照GET方式请求访问HelloServlet,那么就只需要重新实现doGet()方法:
public class HelloServlet extends HttpServlet{
public void doGet(
HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException{
//提供具体的实现代码
}
}
如果客户端会按照GET或POST方式请求访问HelloServlet,并且在这两种方式下,HelloServlet提供同样的服务,那么可以在HelloServlet类中重新实现doGet()方法,并且让doPost()方法调用doGet()方法:
public class HelloServlet extends HttpServlet{
public void doGet(
HttpServletRequest request,HttpServletResponse response){
//提供具体的实现代码
}
public void dePost(
HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException{
doGet(request,reponse);
}
}
ServletRequest接口
在Servlet接口的service(ServletRequest requset,ServletResponse response)方法中有一个ServletRequest类型的参数。ServletRequest类表示来自客户端的请求。当Servlet容器接收到客户端要求访问特定Servlet的请求时,容器先解析客户端的原始请求数据,把它包装成一个ServletRequest对象。当容器调用Servlet对象的service()方法时,就可以把ServletRequest对象作为参数传给service()方法。
● ServletRequest接口提供了一系列用于读取客户端的请求数据的方法。
● getContentLength():返回请求正文的长度。如果请求正文的长度未知,则返回-1;
● getContentType():获得请求正文的MIME类型。如果请求正文的类型未知,返回null。
● getInputStream():返回用于读取请求正文的输入流。
● getLocalAddr():返回服务器端的IP地址。
● getLocalName():返回服务器端的主机名。
● getLocalPort():返回服务器端的端口号。
● getParameter(String name):根据给定的请求参数名,返回来自客户请求中的匹配的请求参数值。
● getProtocol():返回客户端与服务器端通信所用的协议的名称及版本号。
● getReader():返回用于读取字符串形式的请求正文的BufferedReader对象。
● getRemoteAddr():返回客户端的IP地址。
● getRemoteHost():返回客户端的主机名。
● getRemotePort():返回客户端的端口号。
此外,在ServletRequest接口中还定义了一组用于在请求范围内存取共享数据的方法。
● setAttribute(String name,Object object):在请求范围内保存一个属性,参数name表示属性名,参数object表示属性值。
● getAttribute(String name):根据name参数给定的属性名,返回请求范围内的匹配的属性值。
● removeAttribute(String name):从请求范围内删除一个属性。
1.7.5 HttpServletRequest接口
HttpServletRequest接口是ServletRequest接口的子接口。HttpServlet类重载service()方法及doGet()和doPost()等方法都有一个HttpServletRequest类型的参数:
protected void service(HttpServletRequest request,
HttpServletResponse response)throws ServletException,IOException
HttpServletRequest接口提供了用于读取HTTP请求中的相关信息的方法:
● getContextPath():返回客户端所请求访问的Web应用的URL入口。例如,如果客户堵啊访问的URL为http://java.itxdl.cn/helloapp/info.html,那么该方法返回"/helloapp"。
● getCookie():返回HTTP请求中的所有Cookie。
● getHeader(String name):返回HTTP请求头部的特定项。
● getHeaderNames():返回一个Enumeration对象,它包含了HTTP请求头部的所有项目名。
● getMethod():返回HTTP请求方式。
● getRequestURI():返回HTTP请求的头部的第一行中的URI。
● getQueryString():返回HTTP请求中的查询字符串,即URI中的"?"后面的内容。
解析原始请求数据非常繁琐,而依据Servlet API来创建Servlet,则无需费力地解析原始HTTP请求。解析原始HTTP请求的工作完全由Servlet容器代劳了。Servlet容器把HTTP请求包装成HttpServletRequest对象,Servlet只需要调用该对象的getXXX()方法,就能轻轻松松的读取HTTP请求中的各种数据。
在下面的案例中,RequestInfoServlet类通过访问HttpServletRequest对象的各种方法来读取HTTP请求中的特定信息,并且把他们写入到HTML文档中。
package com.xdl.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class RequestInfoServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("
LocalAddr:" + request.getLocalAddr());
out.println("
LocalName:" + request.getLocalName());
out.println("
LocalPort:" + request.getLocalPort());
out.println("
Protocol:" + request.getProtocol());
out.println("
RemoteAddr:" + request.getRemoteAddr());
out.println("
RemoteHost:" + request.getRemoteHost());
out.println("
RemotePort:" + request.getRemotePort());
out.println("
Method:" + request.getMethod());
out.println("
URI:" + request.getRequestURI());
out.println("
ContextPath:" + request.getContextPath());
out.println("
QueryString:" + request.getQueryString());
out.println("
***打印HTTP请求头***");
Enumeration eu = request.getHeaderNames();
while(eu.hasMoreElements()){
String headerName = (String)eu.nextElement();
out.println("
" + headerName + ": "
+ request.getHeader (headerName));
}
out.println("
***打印HTTP请求头结束***");
out.println("
username:" + request.getParameter("username"));
out.close();
}
}
在web.xml文件中为RequestInfoServlet类映射的URL为"/info",通过浏览器访问http://localhost:8080/xdl/info?username=Tom,将出现如图1.30所示的由RequestInfoServlet生成的HTML页面。
RequestInfoServlet生成的HTML页面
ServletResponse接口
在Servlet接口的service(ServletRequest request,ServletResponse response)方法中有一个ServletResponse类型的参数。Servlet通过ServletResponse对象类生成响应结果。当Servlet容器接收到客户端要求访问特定Servlet请求时,容器会创建一个ServletResponse对象,并把它作为参数传给Servlet的service()方法。
在ServletResponse接口中定义了一系列与生成响应结果相关的方法。
● setCharacterEncoding(String charset):设置响应正文的字符编码。响应正文的字符编码默认是ISO-8859-1。
● setContentLength(int len):设置响应正文的长度。
● setContentType(String type):设置响应正文的MIME类型。
● getCharacterEncoding():返回响应正文的字符编码。
● getContentType():返回响应正文的MIME类型。
● setBufferSize(int size):设置用于存放响应正文数据的缓冲区大小。
● getBufferSize():获取用于存放响应正文数据的缓冲区大小。
● reset():晴空缓冲区内的正文数据,并且清空响应状态代码及响应头。
● resetBuffer():仅仅清空缓冲区内的正文数据,不清空响应状态码及响应头。
● flushBuffer():强制性地把缓冲区内的响应正文数据发送到客户端。
● isCommitted():返回一个boolean类型的值。如果为true,表示缓冲区内的数据已经提交给客户,即数据已经发送到客户端。
● getOutputStream():返回一个ServletOutputStream对象,Servlet用它来输出二进制的正文数据。
● getWriter():返回一个PrintWriter对象,Servlet用它来输出字符串形式的正文数据。
Servlet通过ServletResponse对象主要产生HTTP响应结果的正文部分。ServletResponse的getOutputStream()方法返回一个ServletOutputStream对象,Servlet可以利用ServletOutputStream来输出二进制的正文数据。ServletResponse的getWriter方法返回一个PrintWriter对象,Servlet可以利用PrintWriter来输出字符串形式的正文数据。
为了提高输出数据的效率,ServletOutputStream和PrintWriter先把数据写入缓冲区内。当缓冲区内的数据被提交给客户后,ServletResponse的isCommitted()方法返回true。在以下几种情况下,缓冲区内的数据会被提交给客户,即数据被发送到客户端:
当缓冲区内的数据已经满时,ServletOutputStream或PrintWriter会自动把缓冲区内的数据发送给客户端,并且清空缓冲区。
Servlet调用ServletResponse对象的flushBuffer()方法。
Servlet调用ServletOutputStream或PrintWriter对象的flush()方法或close()方法。
为了确保ServletOutputStream或PrintWriter输出的所有数据都会被提交给客户,比较安全的做法是在所有数据都输出完毕后,调用ServletOutputStream或PrintWriter的close()方。
Servlet还可通过ServletResponse对象的setContentLength()、setContentType()和setCharacterEncoding()来分别设置响应正文的长度、MIME类型和字符编码。在ServletResponse接口的实现类中,这些setXXX()方法会把相应的设置写到HTTP响应头中。
值得注意的是,如果要设置响应正文的MIME类型和字符编码,必须先调用ServletResponse对象的setContentType()和setCharacterEncoding()方法,然后在调用ServletREsponse的getOutputStream()或PrintWriter()方法,或者提交缓冲区内的正文数据。只有满足这样的操作顺序,所做的设置才能生效。
1.7.7 HttpServletResponse接口
HttpServletResponse接口是ServletResponse的子接口,HttpServlet类的重载service()方法及doGet()和doPost()等方法都有一个HttpServletResponse类型的参数:
protected void service(HttpServletRequest request,
HttpServletResponse response)throws ServletException,IOException
HttpServletResponse接口提供了与HTTP协议相关的一些方法,Servlet可通过这些方法来设置HTTP响应头或项客户端写Cookie。
● addHeader(String name,String value):项HTTP响应头中加入一项内容。
● sendError(int sc):向客户端发送一个代表特定错误的HTTP响应状态代码。
● sendError(int sc,String value):向客户端发送一个代表特定错误的HTTP响应状态码,并且发送具体的错误信息。
● setHeader(String name,String value):设置HTTP响应头中的一项内容。如果在响应头中已经存在这项内容,那么原来所做的设置将被覆盖。
● setStatus(int sc):设置HTTP响应的状态代码。
● addCookie(Cookie cookie):向HTTP响应中加入一个Cookie。
在HttpServletRequest接口中定义了一些代表HTTP响应状态代码的静态常量,如下所示:
● HttpServletResponse.SC_BAD_REQUEST:对应的响应状态代码为400。
● HttpServletResponse.SC_FOUND:对应的响应状态代码为302。
● HttpServletResponse.SC_METHOD_NOT_ALLOWED:对应的响应状态代码为405。
● HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION:对应的响应状态代码为203。
● HttpServletResponse.SC_FORBIDDEN:对应的响应状态代码为403。
● HttpServletResponse.SC_NOT_FOUNT:对应的响应状态代码为404。
● HttpServletResponse.SC_OK:对应的响应状态代码为200。
下面的例子中,HelloServlet类的doGet()方法先得到username请求参数,对其进行中文字符编码转换(在tomcat8中,已经不需要对GET请求参数进行编码转换了),然后判断username参数是否为null。如果满足条件,就直接返回一个代表特定错误的403响应状态代码;否则,就通过HttpServletResponse对象的getWriter()方法得到一个PrintWriter对象,然后通过PrintWriter对象来输出一个HTML页面。
package com.xdl.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
if(username!=null){
//使用Tomcat8不需要对请求参数的编码进行转换
//username = new String(username.getBytes("ISO_8859-1"),"UTF-8");
}else{
response.sendError(response.SC_FORBIDDEN);
return;
}
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("你好:" + username);
out.close();
}
}
以上HelloServlet类利用HttpServletResponse对象的setContentType()方法来设置响应正文的MIME类型及字符编码。"text/html"表示响应正文为HTML文档,"UTF-8"表示响应正文采用UTF-8字符编码,以下3种方式是等价的,都能设置HTTP响应正文的MIME类型及字符编码:
response.setContentType("text/html;charset=UTF-8");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
response.setHeader("content-type","text/html;charset=UTF-8");
在web.xml文件中为HelloServlet类映射的URL为"/hello",通过浏览器访问http://localhost:8080/xdl/hello?username=小王,将出现如图1.31所示的由HelloServlet生成HTML页面。
HelloServlet生成的HTML页面
通过浏览器访问http://localhost:8080/xdl/hello,则将出现如图1.32所示的由HelloServlet生成的错误页面。
HelloServlet生成的错误页面
ServletConfig接口
Servlet接口的init(ServletConfig config)方法有一个ServletConfig类型的参数。当Servlet容器初始化一个Servlet对象时,会为这个Servlet对象创建一个ServletConfig对象。在ServletConfig对象中包含了Servlet的初始化参数信息,此外,ServletConfig对象还与当前Web应用的ServletContext对象关联。Servlet容器在调用Servlet对象的init(ServletConfig config)方法时,会把ServletConfig对象作为参数传给Servlet对象,init(ServletConfig config)方法会使得当前Servlet对象与ServletConfig对象之间建立关系。
在ServletConfig接口中定义了以下方法:
● getInitParameter(String name):根据给定的初始化参数名,返回匹配的初始化参数值。
● getInitParameter():返回一个Enumeration对象,里面包含了所有的初始化参数名。
● getServletContext():返回一个ServletContext对象。
● getServletName():返回Servlet的名字,即web.xml文件中相应 每个初始化参数包括一对参数名和参数值。在web.xml文件中配置一个Servlet时,可以通过 以下代码为一个FontServlet类设置了两个初始化参数:color参数和size参数。
HttpServlet类继承GenericServlet类,而GenericServlet类实现了ServletConfig接口,因此在HttpServlet类或GenericServlet类及子类中都可以直接调用ServletConfig接口中的方法。
在下面的例子中,FontServlet类演示了ServletConfig接口的用法。FontServlet类通过getInitParameter()方法读取color参数和size参数的值,然后依据这两个参数来设置待输出的word字符串的颜色和大小。
package com.xdl.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class FontServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String word = request.getParameter("word");
if(word==null){
word = "hello";
}
String color = getInitParameter("color");
String size = getInitParameter("size");
System.out.println("servletname:" + getServletName());
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println(""
+word +"");
out.close();
}
}
在web.xml文件中为FontServlet类映射的URL为"/font",通过浏览器访问http://localhost:8080/xdl/font?word=HelloWorld,FontServlet将输出如图1.33所示的HTML文档。
FontServlet生成的HTML页面
ServletContext接口
ServletContext是Servlet与Servlet容器之间直接通信的接口。Servlet容器在启动一个Web应用时,会为它创建一个ServletContext对象。每个Web应用都有唯一的ServletContext对象,可以把ServletContext对象地理解为Web应用的总管家,同一个Web应用中的所有Servlet对象都共享一个总管家,Servlet对象们可以通过这个总管家来访问容器中的各种资源。
ServletContext接口提供的方法可以分为以下几种类型:
1.用于在Web应用范围内存取共享数据的方法
(1)setAttribute(String name):根据参数给定的属性名,返回一个Object类型的对象,它表示ServletContext中与属性名匹配的属性值。
(2)getAttributeNames():返回一个Enumeration对象,该对象包含了所有存放在ServletContext中的属性名。
(3)removeAttribute(String name):根据参数指定的属性名,从ServletContext中删除匹配的属性。
2.访问当前Web应用的资源
(1)getContextPath():返回当前Web应用的URL入口。
(2)getInitParameter(String name):根据给定的参数名,返回Web应用范围内的匹配的初始化参数值。在web.xml文件中,直接在
(3)getinitParameternames():返回一个Enumeration对象,它包含了Web应用范围内的所有初始化参数名。
(4)getServletContextname():返回Web应用的名字,即web.xml文件中
(5)getRequestDispather(String path):返回一个用于向其他Web组件转发请求的RequestDispatcher对象。
3.访问Servlet容器中的其他Web应用
getContext(String uripath):根据参数指定的URI,返回当前Servlet容器中其他Web应用的ServletContext对象。
● 访问Servlet容器的相关信息
(1)getMajorVersion():返回Servlet容器支持的Java Servlet API的主版本号。
(2)getMinorVersion():返回Servlet容器支持的java Servlet API的此版本号。
(2)getServletInfo():返回Servlet容器的名字和版本。
4.访问服务器端的文件系统资源
(1)getRealpath(String path):根据参数指定的虚拟路径,返回文件系统中的一个真实的路径。
(2)getResource(String path):返回一个映射到参数指定的路径的URL。
(3)getResourceAsStream(String path):返回一个用于读取参数指定的文件输入流。
(4)getMimeType(String file):返回参数指定的文件的MIME类型。
5.输出日志
(1)log(String msg):向Servlet的日志文件中写日志。
(2)log(String message,Throwable throwable):向Servlet的日志文件中写错误日志,以及异常的堆栈信息。
在ServletConfig接口中定义了getServletContext()方法。HttpServlet类继承GenericServlet类,而GenericServlet类实现了ServletConfig接口,因此在HttpServlet类或GenericServlet类及子类中都可以直接调用getServletContext()方法,从而得到当前Web应用的ServletContext对象。