本章节介绍了HTTP Servlet的基本概念,概述了如何在Apusic应用服务器上进行HTTP Servlet的开发。
Servlet是基于Java技术的Web组件,用来扩展以请求/响应为模型的服务器的能力。Servlets可以响应任何类型请求,但我们通常使用Http Servlets来处理HTTP请求(request)和提供HTTP响应(response)。
Servlet继承层次
Apusic HTTP Servlets通常用来创建交互式的应用:以标准Web浏览器作为客户端表示,Apusic应用服务器处理服务器端的商业逻辑。Apusic HTTP Servlets可访问数据库、EJB 、消息API 、HTTP Session和其它Apusic应用服务器提供的其他功能。
Apusic服务器完全支持最新的Servlet2.5规范。HTTP Servlets是JavaEE标准的组成部分。
创建动态Web页面。使用HTML表单得到最终用户的输入,并提供HTML页面作为输出,响应用户的请求。例如在线购物车,金融服务和个性化内容等。
生成如在线会议等协作系统。
运行在Apusic应用服务器上的Servlets可以获得各种不同的API和系统服务的支持,如:
Session跟踪——使Web站点可以跨越多个页面跟踪用户的过程。这个功能支持Web站点提供类似于电子商务中的购物车。Apusic应用服务器在集群服务器之间提供Session迁移。
JDBC驱动程序——可以使用JDBC驱动程序提供的基本的数据库访问服务,同时也可以使用Apusic应用服务器提供的数据库连接池、服务器端的数据缓存和事务处理等多种服务。
可对Servlets使用不同的安全类型,如使用SSL来提供安全通讯。
可使用EJB来封装Sessions 、数据库中的数据和其他功能。
JMS——允许Servlet与其他Servlet和程序交换消息。
Servlets可使用标准JDK API 。
可传递一个请求(request)到其他Servlet或其他资源。
为任何其他J2EE兼容的Servlet引擎编写的Servlet可以轻松的部署到Apusic应用服务器上。
Servlets和JSP可以一起创建Web应用。
HTTP Servlets程序员使用标准的Java API(javax.servlet.http)来创建交互式应用程序。
HTTP Servlets可以读取HTTP头信息,并向客户端浏览器发送HTML代码。
在Apusic应用服务器中,Servlet作为web应用的一部分部署。Web应用是一个由诸如servlet类、JSP、静态Web页面、图像、安全描述等应用组件组成的。
Servlet2.5规范作为JavaEE的组成部分,定义了Servlet API的实现和Servlet如何部署在企业应用中。在兼容服务器如Apusic应用服务器上部署Servlet ,是通过将组成企业应用的Servlet和其他资源打包到一个"Web应用"中来完成的。Web应用通过特定的目录结构来容纳资源和部署描述。Web应用可使用后缀是".war" 的压缩文件包部署。
Apusic应用服务器支持Servlet 2.5规范,可以从Sun Microsystems的网站获得这些文档:
本章节主要介绍Apusic Http Servlets部分跟随规范变动和升级的内容
Servlet 2.5规范现在把J2SE 5.0(JDK 1.5)作为最低要求。这个变动保证了J2SE5.0中所有新的语言特性都在Servlet 2.5中可用,包括:泛型支持、自动包装、增强的迭代循环、新的enum枚举类型、静态导入、可变参数表、元数据注解等等
注解提供了一套机制,通过元数据来装饰Java代码结构(类、方法、域等等)。注解并不象代码一样执行,而是对代码进行标记,使代码处理机可以根据这些元数据信息作出相应的行为。以前的版本提供了一些不同的小技巧来对类或方法进行注解,例如使用Serializable接口来标记序列化类,或使用@deprecated的Javadoc注释来标记弃用的方法。新的元数据机制提供一套标准的方法来对代码进行注解,并提供了类库来创建自定义的注解类型。请参考第 43.2.4.2 节 “使用注解”
Servlet 2.5中引入了一些对web.xml的小变动,使得部署配置更为方便。
当编写<filter-mapping>元素时,现在可以在<servlet-name>中使用*号来代表多个servlet(当然也包括了多个jsp)。例如:
<filter-mapping> <filter-name>Image Filter</filter-name> <servlet-name>*</servlet-name> <!-- 新特性 --> </filter-mapping>
或
<filter-mapping> <filter-name>Dispatch Filter</filter-name> <servlet-name>*</servlet-name> <dispatcher>FORWARD</dispatcher> </filter-mapping>
现在一个<servlet-mapping>元素中可以包含多个<url-pattern>元素,例如:
<servlet-mapping> <servlet-name>color</servlet-name> <url-pattern>/color/*</url-pattern> <url-pattern>/colour/*</url-pattern> </servlet-mapping>
同样地,现在一个<filter-mapping>可以包含多个<url-pattern>与<servlet-name>元素,例如:
<filter-mapping> <filter-name>Multipe Mappings Filter</filter-name> <url-pattern>/foo/*</url-pattern> <servlet-name>Servlet1</servlet-name> <servlet-name>Servlet2</servlet-name> <url-pattern>/bar/*</url-pattern> </filter-mapping>
Servlet 2.4版使用XML Schema定义作为部署描述文件,这样使Web容器更容易校验web.xml语法。同时XML Schema提供了更好的扩充性。
DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
变为在可以根元素指定属性,当然以前的方式也兼容
version="2.4" 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 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
Servlet 2.4在事件监听器中加入了ServletRequest监听器,包括新的API:ServletRequestListener, ServletRequestAttributeListener和其他相关类。这些类可以用来管理和控制与ServletRequest动作有关的事件。请参考第 43.2.5.2 节 “Application Events”
Servlet 2.4的Web程序增强了filter和request dispatcher的配合功能,这样过滤器可以根据请求分发器(request dispatcher)所使用的方法有条件地对Web请求进行过滤。请参考:第 43.2.5.1 节 “Servlet Filtering”
本章节介绍了基本的Http Servlet编程知识。
Servlet的生命周期是由运行Servlet的容器控制的。当一个请求(request)被映射到Servlet上时,容器会按照下面的步骤执行:
如果Servlet的实例不存在,那么容器
1. 装载Servlet类
2. 创建这个Servlet类的一个实例
3. 调用Servlet的init方法来初始化。相关的讨论在第 43.2.4.1 节 “初始化Servlet”
调用 Servlet 的service方法,传递request和response对象。相关的讨论在第 43.2.4.3 节 “编写Service方法” 。
如果容器需要移去一个Servlet,它调用destroy方法来结束这个Servlet。相关的讨论在第 43.2.4.9 节 “结束一个Servlet”。
Servlet 的生命周期
这一节描述了编写一个简单的Http Servlet的基本步骤。
1. 引入相应的包和类,包括
import javax.servlet.*; import javax.servlet.http.*; import java.io.*;
2. 继承javax.servlet.http.HttpServlet
public class MyServlet extends HttpServlet{
3. 实现service方法
Servlet的主要功能是接受从浏览器发送过来的HTTP请求(request),并返回HTTP响应(response)。这个工作是在service方法中完成。service方法包括从request对象获得客户端数据和向response对象创建输出。
如果一个servlet从javax.servlet.http.HttpServlet继承,实现了doPost或doGet方法,那么这个servlet只能对POST或GET作出响应。如果开发人员想处理所有类型的请求(request),只要简单的实现service方法即可(但假如选择实现service方法,则不必实现doPost或doGet方法,除非在service方法的开始调用super.service())。
Service方法对请求的处理
HTTP Servlet规范描述了用来处理其他请求(request)类型的方法,所有这些方法都可以归属于service方法。 所有的service 方法使用一样的参数。HttpServletRequest提供关于请求(request)的信息,servlet可以使用 HttpServletResponse 对HTTP客户端作出响应。
public void service(HttpServletRequest req,HttpServletResponse res) throws IOException{
4. 设置响应内容的类型:
res.setContentType("text/html");
5. 获得java.io.PrintWriter对象的引用,用来输出:
PrintWriter out = res.getWriter();
6. 使用PrintWriter对象的println()方法创建一些HTML代码,例如:
out.println("<html><head><title>Hello World!</title></head>"); out.println("<body><h1>Hello World!</h1></body></html>"); } }
7. 编译Servlet:
从存放此Servlet源代码文件的目录编译此Servlet到包含此Servlet的应用中的WEB-INF/classes目录。如:
javac -d /your_application_dir/WEB-INF/classes your_servlet.java
8. 将此Servlet作为应用的一部分部署,请参看 第 48.2 节 “打包和部署Web模块”。
9. 从浏览器访问Servlet
一般说来,调用Servlet的URL取决于包含Servlet的Web应用的名字和Web应用部署描述中的Servlet映射的名字。请求(request)参数也可是调用Servlet的URL的一部分,一般Servlet的URL如以下模式:
http://APUSIC_ADDRESS:<port>/your_web_application_name/mapped_servlet_name? parameter
通过前面的步骤创建了一个基本的Servlet,开发人员也许需要使用一些Servlet的高级特性:
处理HTML表单—— Http Servlets可以接收和处理从浏览器传送过来的HTML表单,请参考第 43.2.4.3.2 节 “提供HTTP响应”
应用的设计—— Http Servlets 提供了许多方法来设计开发人员的应用:
初始化servlet——如果Servlet在初始化时需要初始化数据,获得初始化参数,或者执行一些其他的动作,那么就需要覆盖init()方法,请参考第 43.2.4.1 节 “初始化Servlet”
使用Session可以跟踪用户的信息,请参考第 43.2.4.5 节 “维护客户端状态”
本节介绍了如何在Apusic应用服务器的环境下编写Http Servlets。
当Web容器装载和实例化一个Servlet类,在这个Servelt接收客户请求之前,Web容器需要初始化Servlet。开发人员可以覆盖Servlet接口的init()方法,允许Servlet读取配置信息、初始化资源和执行其他任何一次性的行为。Servlet初始化过程的失败将抛出UnavailableException异常。
启动Apusic应用服务器时初始化Servlet
通过设定Apusic Web应用的部署描述文件中Servlet类的<load-on-startup>标记,可使服务器在启动时初始化Servlet。同时,通过设定包含此Servlet的Web应用部署描述中的参数,可传递初始化参数到此Servlet 。
下例在Web应用部署描述文件中设定了一个名为“company_name”,值为“Apusic”的初始化参数,
<servlet> ... <init-param> <param-name>company_name</paramname> <param-value>Apusic</param-value> </init-param> </servlet>
通过调用getInitParameter(String name)方法可取得此参数。
重载init()方法
通过重载init()方法可以定制Servlet初始化的动作。下面代码重载了init()方法,并使用了前面定义的company_name初始化参数。
String company_name; public void init(ServletConfig config) throws ServletException{ company_name=getInitParameter("company_name"); }
Servlet2.5规范对某些注解如何在servlet环境中运作进行了规定。简单的servlet容器可以忽略这些规则,但JavaEE容器中的servlet则必须遵循这些规定。某些注解提供了部署信息,作为web.xml部署描述文件的替代品;某些注解请求容器进行某些动作,否则servlet就需要自行去实现这些任务。还有些注解则同时兼具以上两种功用。这些注解并不是由Servlet2.5规范定义的,规范仅仅解释了这些注解如何在servlet环境中运作。以下列出一些在JavaEE5中常见的注解以及它们的使用意图:
@Resource用来标记一个类或变量,请求servlet容器对其进行“资源注入”。当容器遇到这个注解时,将在servlet进入服务状态之前对变量赋予合适的值。通过这个注解,避免了在代码中显式使用一个JNDI查找调用以及在web.xml中声明资源。服务器自动通过反射机制完成这两个任务。注入资源可以是数据源、JMS目的地、或环境变量。以下是个简单的例子:
@Resource javax.sql.DataSource catalog; public getData() { Connection con = catalog.getConnection(); }
在servlet进入服务状态之前,容器将自动通过JNDI找到名为catalog,类型为DataSource的变量并将其引用赋值给此处的catalog变量。
@Resources注解作用与@Resource相似,用来包含一系列的@Resource注解
这两个注解把一个或多个类方法标记为生命周期回调方法。标记为@PostConstruct的方法将在资源注入后被回调,提供机会对注入的资源进行初始化。标记为@PreDestroy的方法将在servlet停止服务前被回调,提供机会释放注入的资源。这些回调方法必须为无返回值(void)且不抛出任何checked异常的非静态方法。在servlet中,这些注解的作用基本上是可以让任意方法成为第二个init()或destroy()方法。
@PersistenceContext,@PersistenceContexts,@PersistenceUnit,@PersistenceUnits:在EJB3.0规范中定义的用于持久对象的注解。
Servlet通过下面的方法来提供服务:
实现GenericServlet的service方法
实现HttpServlet的doMethod方法(doGet、doDelete、doOptions、doPost、doPut、doTrace)
实现Servlet接口并提供任何其他协议相关的方法。
通常,service方法用来从客户请求(request)中提取信息,访问扩展资源,并基于上面的信息提供响应(response)。
对于HTTP servlets,正确提供响应的过程是首先填写响应(response)的头信息,然后从响应(response)中得到输出流,最后向输出流中写入内容信息。响应(response)头信息必须最先设置。下面两节将描述从请求(request)中获得信息和产生HTTP响应(response)。
客户端请求(request)包含了从客户端传递到Servlet的数据。所有的请求(request)都实现了ServletRequest接口。这个接口定义了一些方法来访问下面的信息:
类型描述 | 对应方法 |
参数,用来在客户端和Servlet之间传送信息 |
|
对象值属性,用来在Servlet容器和Servlet之间,或者协作的Servlet之间传递信息 |
|
有关请求使用的协议信息,客户端和服务器在请求中的调用 |
|
有关localization的信息 |
|
下面的代码片断示范了如何使用request中的方法获得客户端信息。
Enumeration params = request.getParameterNames(); String paramName = null; String[] paramValues = null; while (params.hasMoreElements()) { paramName = (String) params.nextElement(); paramValues = request.getParameterValues(paramName); System.out.println("\nParameter name is " + paramName); for (int i = 0; i < paramValues.length; i++) { System.out.println(", value " + i + " is " + paramValues[i].toString()); } }
HTTP servlets使用HTTP request对象(HttpServletRequest),它包含了request URL,HTTP头信息, 查询字符串等等。
HTTP request URL 包括几个部分:
http://<host>:<port><request path>?<query string>
一般情况下:
requestURI = contextPath + servletPath + pathInfo
Context path:通过getContextPath方法获得
Servlet Path:通过getServletPath方法获得
PathInfo:通过getPathInfo方法获得
例如:
Request Path | Path Elements |
/catalog/help/feedback.jsp |
|
响应(response)包含了在服务器和客户端之间传递的数据。所有的响应(response)都实现了ServletResponse接口。这个接口定义了一些方法提供给开发人员使用:
类型描述 | 对应方法 |
获得向客户端发送数据的输出流 |
|
指示响应返回的内容类型(例如:text/html)。已经注册的内容类型名称保存在IANA(Internet Assigned Numbers Authority):ftp://ftp.isi.edu/in-notes/iana/assignments/media-types |
|
指出是否是缓冲输出。缺省情况下写入输出的内容被立即发送到客户端。使用缓冲后写入输出的内容先不发送到客户端,这样Servlet有更多的时间设置相应的状态码和头信息,或者转移到其他的Web资源 |
|
设置localization信息 |
|
HTTP response类(HttpServletResponse)有一些代表HTTP头信息的域:
状态码,用来指出响应(response)失败的原因
Cookies,在客户端存储应用相关的信息,有时cookies用来维护和标识用户的session。
Servlet首先设置响应(response)头信息:响应(response)的内容类别和缓冲区大小,然后在doGet方法中从响应(response)获得PrintWriter,最后向输出中写入HTML代码,调用close()方法提交这次对客户端的响应(response)。
public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置头信息 response.setContentType("text/html"); response.setBufferSize(8192); PrintWriter out = response.getWriter(); // 向response中输出 out.println("<html>" + "<head><title>+ messages.getString("TitleBookDescription") +</title></head>"); ... out.println("</body></html>"); // 关闭输出流 out.close(); }
HTTP servlets通常具有处理多个客户端并发访问的能力。如果在Servlet中的方法访问了共享资源,那么开发人员需要创建在某一时刻只接收一个客户端请求(request)的Servlet来进行并发控制。
如果Servlet不仅继承HttpServlet类,而且同时实现SingleThreadModel接口,那么Servlet就可以在某一时刻只处理一个客户端请求(request)。
实现了SingleThreadModel接口不需要写任何额外的方法。只要Servlet实现了SingleThreadModel接口,Apusic应用服务器就确保在某一时刻只运行一个service方法。
例如,ReceiptServlet接受用户名和信用卡号,然后处理用户的订单并更新数据库。数据库连接作为共享资源,servlet应该同步对数据库连接的访问,或者servlet实现SingleThreadModel接口。
public class ReceiptServlet extends HttpServlet implements SingleThreadModel { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... } ... }
许多应用需要获得一系列互相关联的客户端请求,例如购物车功能。因为HTTP是无状态的协议,Web应用应该维护这些状态,这就叫做session。为了使应用具有session功能,Java Servlet技术提供了管理session的API和集中实现session的机制。
Session表现为HttpSession对象。调用request对象的getSession方法访问session。这个方法返回当前客户端请求(request)所关联的session,如果不存在,则为当前请求(request)创建一个session。由于getSession方法可能会改变响应(response)的头信息,所以需要在获得PrintWriter或ServletOutputStream之前被调用。
可以通过名称使对象值属性和一个session相关联。例如把购物车作为属性存储在session中,在其他Servlet中可以通过session再获得购物车。
// 得到用户session和购物篮 HttpSession session = request.getSession(); ShoppingCart cart = (ShoppingCart)session. getAttribute("cart"); ...
由于没有办法知道HTTP客户端不再需要session,因此每个session都关联一个时间期限使它的资源可以被回收。通过session的setMaxInactiveInterval和getMaxInactiveInterval方法访问超时时间。
为了确保session的有效,不超时,开发人员应该在service方法中周期性的访问session。当客户端完成一个(组)完整的交互过程后,可以使用invalidate()方法使服务器端的session无效,并清除session数据。
// 得到用户session和购物篮 HttpSession session = request.getSession(); // 付款完成,使session无效 session.invalidate(); ...
Web容器使用了一些方法使用户和特定的session相关联,这些方法在客户端与服务器端之间传递session的标识。这个标识可以作为cookies在客户端被维护,或者Web组件把这个标识包含在每个URL中返回到客户端。
如果应用需要使用session对象,那么开发人员必须确保在用户关闭cookies的情况下,应用能够改写URL使session跟踪功能激活。在所有返回给用户URL之前都调用response的encodeURL(URL)方法,这样在用户关闭cookies的情况下URL中就会包含session ID,否则不改变URL。 例如:
out.println("<p> <p><strong><a href=\"" + response.encodeURL(request.getContextPath() + "/catalog") + "\">" + messages.getString("ContinueShopping") + "</a> " + "<a href=\"" + response.encodeURL(request.getContextPath() + "/cashier") + "\">" + messages.getString("Checkout") + "</a> " + "<a href=\"" + response.encodeURL(request.getContextPath() + "/showcart?Clear=clear") + "\">" + messages.getString("ClearCart") + "</a></strong>");
上面的代码改写了三个URL,如果客户端关闭 cookies,则URL被改写为:
http://localhost:6888/bookstore1/cashier; jsessionid=wKgUUxroPN$HVmpTkhU6YPLTqyMA
如果客户端cookies未关闭,则URL不作任何改变:
http://localhost:6888/bookstore1/cashier
Web组件中的环境(context)是实现了ServletContext接口的对象。可以通过getServletContext方法获得Web环境(Web context)。通过一些方法可以访问Web环境(ServletContext),以得到如下一些信息或资源:
初始化参数
与Web环境相关的资源
对象值属性
记录日志
//获得ServletContext ServletContext context = getServletContext(); Counter counter = (Counter)context.getAttribute("hitCounter"); ... writer.println("The number of hits is: " + counter.incCounter()); ... context.log(sw.getBuffer().toString());
在Apusic应用服务器的编程模型中,业务逻辑(包括数据库访问、事务和复杂计算等)被包装在EJB组件中。作为表现层,Servlet能够通过Java Naming Directory Interface (JNDI)访问EJB组件。首先访问JNDI获得EJB组件的代理,然后可以象引用普通对象一样访问EJB组件。所有这一切,都是由EJB容器进行管理。下面的代码展示了通过JNDI定位EJB:
InitialContext ic = new InitialContext(); Object objRef = ic.lookup("java:comp/env/ejb/BookDBEJB"); BookDBEJBHome home = (BookDBEJBHome)PortableRemoteObject.narrow(objRef, database.BookDBEJBHome.class); bookDBEJB = home.create();
当编写Http Servlet时,可以使用许多Apusic应用服务器提供的系统服务:
Web组件可以通过两种方式调用其他Web资源:间接方式和直接方式。间接方式调用是指在Web组件的内容中包含了指向其他Web资源的URL。
Web组件也能够在它执行的时候调用其他的Web资源。有两种可能性:
包含其他Web资源的内容
传递请求(request)到其他Web资源
调用方式 | 描述 |
间接方式 | 包含了指向其他Web资源的URL |
直接方式 |
|
为了调用运行Web组件的服务器上的可用资源,必须首先通过getRequestDispatcher("URL") 方法获得RequestDispatcher对象。
开发人员可以从request或Web Context获得RequestDispatcher对象,然而这两种方式有细微的区别。
从request获得的RequestDispatcher对象,可以接受相对路径作为参数
从Web Context获得的 RequestDispatcher对象,需要绝对路径作为参数
包含其他Web资源非常有用,例如从Web组件返回给客户端的响应(response)中可以包含banner或版权(copyright)信息。调用RequestDispatcher对象的相关方法来实现此功能:
include(request, response)
如果资源是静态的,include方法能够实现服务器端包含。如果资源是Web组件,那么执行include()方法的效果等同于:
1. 向被包含的Web组件发送请求
2. 执行Web组件
3. 把执行的结果返回给包含Web组件的Servlet
被包含的Web组件可以访问request对象,当访问response对象有一些限制:
可以向response对象写入内容和提交这个response
不能设置头信息或调用任何可能影响头信息的方法
以下BannerServlet用来产生Banner信息:
public class BannerServlet extends HttpServlet { public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println(" .... "); } public void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println(" ... "); } }
其他的servlet可以包含BannerServlet:
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/banner"); if (dispatcher != null) dispatcher.include(request, response); }
在一些应用中,也许需要一个Web组件对请求(request)作预处理,然后利用另一个Web组件产生响应(response)。可以调用RequestDispatcher对象的forward()方法实现这个功能。当一个请求(request)被转移,request URL会被设置成新的页面。如果原先的URL还有用,可以把它作为request的属性保存。以下Dispatcher类,从request中获得RequestDispatcher对象,然后把控制权交给template.jsp:
public class Dispatcher extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { request.setAttribute("selectedScreen",request.getServletPath()); //从 request 中获得 RequestDispatcher 对象 RequestDispatcher dispatcher = request.getRequestDispatcher("/template.jsp"); //传递控制到template.jsp if (dispatcher != null) dispatcher.forward(request, response); } public void doPost(HttpServletRequest request, ... }
forward()方法使另外的资源来答复客户端的请求(request)。如果已经访问了ServletOutputStream或PrintWriter,那么将不能使用include()方法,否则会抛出IllegalStateException异常。
当Servlet容器决定结束一个Servlet时(例如,Servlet容器将要关闭,或Servlet容器回收内存资源),它会调用Servlet接口的destroy方法。在destroy方法中,Servlet释放所有使用的资源并保存所有持久性状态。例如下面的destroy()方法释放了初始化 Servlet时分配的数据库辅助处理对象:
public void destroy() { bookDB = null; }
当需要结束一个Servlet时,必须完成此Servlet所有的service()方法。只有在处理完所有的客户请求(request)或超过特定的时间期限,服务器才会调用destroy()方法。
过滤器(filter)是Java类,可以改变请求(request)和响应(response)的头信息与内容信息。过滤器不同于其他Web组件的地方是它本身并不创建响应(response),然而它可以依附在任何类型的Web资源上。过滤器截取请求(request),检查和改变request对象、response对象,并可以执行一些其他的任务。过滤器提供的主要功能是:
实现日志功能
实现用户定义的安全功能
调试功能
加密
数据压缩
改变发送给客户端的响应(response)
过滤器截获对特定命名的一个资源和一组资源的请求(request),然后执行过滤器中的代码。对于特定的资源,可以指定按照一定顺序调用的一个和多个过滤器,这就组成了链(chain)。使用过滤器主要包括:
编写过滤器类
定制请求(request)和响应(response)
为特定的Web资源指定过滤器链
编写过滤器的API是javax.servlet包中Filter、FilterChain和FilterConfig接口中定义的一些方法。定义一个过滤器就是实现Filter接口。Filter接口中最主要的方法是doFilter()方法,它接收三个参数:request对象、response对象、filterchain对象。
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
这个方法能够执行的动作包括:
检查请求(request)的头信息
定制request对象,改变请求(request)的头信息或数据
定制response对象,改变响应(response)的头信息或数据
调用在过滤器链中的下一个实体。如果当前过滤器是链中的最后一个过滤器,那么下一个实体就是客户请求(request)的资源;否则,链中的下一个过滤器会被调用。通过chain对象的doFilter()方法调用下一个实体,并传递request对象和response对象作为参数。另外,也可以不调用doFilter()方法阻塞请求(request),这样,过滤器应该负责填充对客户的响应(response)。
检查响应的头信息
抛出异常显示处理过程中的错误
除了doFilter()方法,开发人员也必须实现init()和destroy()方法。当容器创建过滤器实例时调用init()方法,
void init(FilterConfig filterConfig)
可以从FilterConfig对象中获得初始化参数。
在doFilter()方法中,过滤器可以从FilterConfig对象获得ServletContext对象,那么就可以访问存储在ServletContext中的属性对象。当过滤器完成特定的处理过程后,调用chain对象的doFilter()方法。例如
public final class HitCounterFilter implements Filter { private FilterConfig filterConfig = null; // 初始化 public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } // 结束 public void destroy() { this.filterConfig = null; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (filterConfig == null) return; StringWriter sw = new StringWriter(); PrintWriter writer = new PrintWriter(sw); writer.println(" ... "); . . . writer.flush(); // 输出日志 filterConfig.getServletContext(). log(sw.getBuffer().toString()); ... //调用在过滤器链中的下一个实体 chain.doFilter(request, wrapper); ... } }
有许多方法可以改变请求(request)或者响应(response)。例如过滤器能够给请求(request)增加属性或在响应(response)中插入数据。
过滤器如果需要改变响应(response)必须在响应(response)返回给客户端之前捕获它。要做到这一点需要传递“替身”流(stream)给Servlet,然后利用“替身”stream产生响应(response)。“替身”stream防止了当响应(response)结束后关闭了原始的响应(response)输出流,并且允许过滤器改变Servlet的响应(response)。
过滤器产生“替身”stream
为了给Servlet传递“替身”stream,过滤器需要创建response对象的包装类并且覆盖getWriter或getOutputStream方法。包装类被FilterChain对象的doFilter方法传递。创建请求(request)的包装类继承ServletRequestWrapper或HttpServletRequestWrapper,创建响应(response)的包装类继承ServletResponseWrapper或HttpServletResponseWrapper。
以下CharResponseWrapper类包装了响应(response):
public class CharResponseWrapper extends HttpServletResponseWrapper { private CharArrayWriter output; public String toString() { return output.toString(); } public CharResponseWrapper(HttpServletResponse response){ super(response); output = new CharArrayWriter(); } public PrintWriter getWriter(){ return new PrintWriter(output); } }
包装类被传递给BookStoreServlet,BookStoreServlet把响应写入“替身”stream,当chain.doFilter返回,HitCounterFilter重新找回response把它写入缓冲,过滤器插入计数器值到缓冲中然后重新设置response的头信息,最后把缓冲中的内容写入response。
PrintWriter out = response.getWriter(); //构造包装类 CharResponseWrapper wrapper = new CharResponseWrapper( (HttpServletResponse)response); //向 doFilter 传递包装类 chain.doFilter(request, wrapper); CharArrayWriter caw = new CharArrayWriter(); caw.write(wrapper.toString().substring(0, wrapper.toString().indexOf("</body>")-1)); ... caw.write("\n</body></html>"); //重新设置响应长度 response.setContentLength(caw.toString().length()); out.write(caw.toString()); out.close();
Web容器使用过滤器映射来决定是否过滤Web资源。在Web应用的部署描述文件中映射过滤器到Servlet或URL模板。
在部署描述文件中加入<filter>标记 ,此标记包括:
<filter-name>:过滤器名称
<filter-class>:过滤器的实现类
<init-params>:过滤器的初始参数
<filter> <filter-name>Compression Filter</filter-name> <filter-class>CompressionFilter</filter-class> <init-param> <param-name>compressionThreshold</param-name> <param-value>10</param-value> </init-param> </filter>
在部署描述文件中加入<filter-mapping>标记,映射过滤器到Servlet:
<filter-mapping> <filter-name>Compression Filter</filter-name> <servlet-name>CompressionTest</servlet-name> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping> <servlet> <servlet-name>CompressionTest</servlet-name> <servlet-class>CompressionTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>CompressionTest</servlet-name> <url-pattern>/CompressionTest</url-pattern> </servlet-mapping>
映射过滤器到URL模板:
<filter> <filter-name>HitCounterFilter</filter-name> <filter-class>HitCounterFilter</filter-class> </filter> <filter-mapping> <filter-name>HitCounterFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping>
使用<url-pattern>/*<url-pattern>标记,此过滤器将使用于此web应用中的任何静态资源或Servlet内容,因为任何URL都可匹配"/*"的URL模式。
dispatcher元素的作用 :Servlet 2.4版的Web程序增强了filter和request dispatcher的配合功能,这样过滤器可以根据请求分发器(request dispatcher)所使用的方法有条件地对Web请求进行过滤。
只有当request直接来自客户,过滤器才生效,对应为REQUEST条件。
只有当request被一个请求分发器使用forward()方法转到一个Web构件时,对应称为FORWARD条件。
只有当request被一个请求分发器使用include()方法转到一个Web构件时,对应称为INCLUDE条件。
只有当request被一个请求分发器使用“错误信息页”机制方法转到一个Web构件时,对应称为ERROR条件。
第五种过滤器作用的条件可以是上面四种条件的组合。
当不使用dispatcher元素时,客户的直接request会被用来过滤请求。如果请求是从一个request dispatcher转发过来的,这个过滤器不工作。
如下图,可以映射一个过滤器到一个或多个Servlet,或者可以映射一个 Servlet到多个过滤器。
过滤器的映射
过滤器F1映射到servlet S1,S2和S3,过滤器F2映射到servlet S2,过滤器F3 映射到servlet S1和S2。
应用事件模型提供了当ServletContext,HttpSession,ServletRequest状态改变时的通知功能。可以编写事件监听类来响应这些状态的改变,并且可以配置和部署应用事件和监听类到Web应用。
对于ServletContext事件,当Web应用部署、卸载和对context增加属性时,事件监听类可以得到通知。下表列出了ServletContext的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。
事件类型 | 接口 | 方法 |
Servlet context被创建 | javax.servlet.ServletContextListener | contextInitialized() |
Servlet context被注销 | javax.servlet.ServletContextListener | contextDestroyed() |
增加属性 | javax.servlet. ServletContextAttributesListener | attributeAdded() |
删除属性 | javax.servlet. ServletContextAttributesListener | attributeRemoved() |
属性被替换 | javax.servlet. ServletContextAttributesListener | attributeReplaced() |
对于HttpSession事件,当session激活、删除或者session属性的增加、删除和替换时,事件监听类得到通知。下表列出了HttpSession的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。
事件类型 | 接口 | 方法 |
session激活 | javax.servlet.http. HttpSessionListener | sessionCreated() |
session删除 | javax.servlet.http. HttpSessionListener | sessionDestroyed() |
增加属性 | javax.servlet.http. HttpSessionAttributesListener | attributeAdded() |
删除属性 | javax.servlet.http. HttpSessionAttributesListener | attributeRemoved() |
属性被替换 | javax.servlet.http. HttpSessionAttributesListener | attributeReplaced() |
对于ServletRequest事件,当request初始化、销毁或者request属性的增加、删除和替换时,事件监听类得到通知。下表列出了ServletRequest的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。
事件类型 | 接口 | 方法 |
session初始化 | javax.servlet.ServletRequestListener | requestInitialized() |
session销毁 | javax.servlet.ServletRequestListener | requestDestroyed() |
增加属性 | javax.servlet.ServletRequestAttributeListener | attributeAdded() |
删除属性 | javax.servlet.ServletRequestAttributeListener | attributeRemoved() |
属性被替换 | javax.servlet.ServletRequestAttributeListener | attributeReplaced() |
配置事件监听类的步骤:
1. 打开Web应用的部署描述文件web.xml
2. 增加事件声明标记<listener>。事件声明定义的事件监听类在事件发生时被调用。<listener>标记必须在<filter>和<filter-mapping>标记之后和<servlet>标记之前。可以为每种事件定义多个事件监听类,Apusic应用服务器按照它们在部署描述文件声明的顺序调用。例如:
<listener> <listener-class>myApp.myContextListenerClass</listener-class> </listener> <listener> <listener-class>myApp.mySessionAttributeListenerClass</listener-class> </listener>
3. 编写和部署监听类。
编写事件监听类的步骤:
1. 创建新的类并实现事件对应的接口
2. 定义不接受参数、访问属性为public的构造函数
3. 实现接口的方法
4. 编译并拷贝到对应Web应用的WEB-INF/classes目录下,或者打包成jar文件拷贝到WEB-INF/lib目录下
ServletContext 监听类例子:
import javax.servlet.*; public final class myContextListenerClass implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { /* 当 ServletContext 初始化时被调用,可以在这儿 初始化 ServletContext 的相关数据 */ } public void contextDestroyed(ServletContextEvent event) { /* 当 Web 应用被卸载或 Apusic 服务器关闭时被调用 */ } }
HttpSession 属性监听类例子:
import javax.servlet.*; public final class mySessionAttributeListenerClass implements HttpSessionAttributesListener { public void attributeAdded(HttpSessionBindingEvent sbe) { /* 增加session属性时被调用 */ } public void attributeRemoved(HttpSessionBindingEvent sbe) { /* 删除session属性时被调用 */ } public void attributeReplaced(HttpSessionBindingEvent sbe) { /* 替换session属性时被调用 */ } }
ServletRequest 属性监听类例子:
import javax.servlet.*; public final class myRequestAttributeListenerClass implements ServletRequestAttributeListener { public void attributeAdded(HttpSessionBindingEvent sbe) { /* 增加request属性时被调用 */ } public void attributeRemoved(HttpSessionBindingEvent sbe) { /* 删除request属性时被调用 */ } public void attributeReplaced(HttpSessionBindingEvent sbe) { /* 替换request属性时被调用 */ } }