当前位置:文档之家› Servlet过滤器简介

Servlet过滤器简介

Servlet过滤器简介
Servlet过滤器简介

14.1 Servlet过滤器简介

很早之前,Servlet API就已成为企业应用开发的重要工具。现在,Servlet中的过滤器和监听器功能则是对J2EE体系的一个补充。过滤器使得Servlet开发者能够在请求到达Servlet之前截取请求,在Servlet处理请求之后修改应答;而Servlet监听器可以监听客户端的请求、服务端的操作,通过监听器,可以自动激发一些操作,如监听应用的启动和停止等。本章将接着介绍Servlet过滤器和监听器的相关知识以及在实际开发中如何使用这些技术。

本章重点:

● Servlet过滤器简介

● Servlet过滤器的实现

● Servlet过滤文本信息

● Servlet监听器简介

14.1 Servlet过滤器简介

Servlet过滤器是小型的Web组件,能拦截请求和响应,以便查看、提取或以某种方式操作正在客户机和服务器之间交换的数据。过滤器是封装了一些功能的W eb组件,这些功能虽然很重要,但是对于处理客户机请求或发送响应来说不是决定性的。典型的例子包括记录关于请求和响应的数据、处理安全协议、管理会话属性等。

一个过滤器可以被关联到任意多个资源,一个资源也可以关联到任意多个过滤器。例如,在图14.1中就有如下的关联关系。

● F1同时被关联到S1、S2、S3。

● F1、F2、F3都和S2关联。

图14.1 Web资源与过滤器相关联

关联到同一个资源的过滤器形成一个过滤器链。例如,对S2进行访问时,F1、F 2、F3就形成了一个过滤器链,客户要访问资源S2,就要经过F1过滤器,然后是F2、F3,最后才是要访问的资源。如果在经过上面这3个过滤器时出现了错误或者被禁止访问,客户的请求就无法到达资源S2。在Servlet过滤器中,这个过滤器传递的过程是通过FilterChain.dofilter()方法实现的,当过滤器链到达末尾时,这个方法调用请求的资源。建立一个过滤器,一般需要下面4个步骤:

(1)建立一个实现Filter接口的类。这个类需要3个方法,即doFilter、ini t和destroy。doFilter方法包含主要的过滤代码(见第(2)步),在init方法中进行一些初始化的设置,而destroy方法进行清除过滤器占用的资源。(2)在doFilter方法中放入过滤行为。doFilter方法的第一个参数为Servle tRequest对象,为过滤器提供了对进入的信息(包括表单数据、cookie和HTTP 请求头)的完全访问。第二个参数为ServletResponse,通常在简单的过滤器中忽略此参数。最后一个参数为FilterChain,用来调用过滤器链中下一个过滤器或者在到达过滤器末尾时调用Servlet或JSP页。

(3)调用FilterChain对象的doFilter方法。Filter接口的doFilter方法将一个FilterChain对象作为它的一个参数。在调用此对象的doFilter方法时,激活下一个相关的过滤器。如果没有另一个过滤器与Servlet或JSP页面关联,则Servlet或JSP页面被激活。

(4)对相应的Servlet和JSP页面注册过滤器。在部署描述符文件(web.xml)中使用filter和filter-mapping元素对需要进行过滤的资源进行配置。

14.2 实现一个Servlet过滤器

Servlet过滤器API包含3个简单的接口,即Filter、FilterChain和FilterC onfig,它们位于javax.servlet包中。从编程的角度看,过滤器类将实现Filt er接口,然后使用这个过滤器类中的FilterChain和FilterConfig接口。该过滤器类的一个引用将传递给FilterChain对象,以允许过滤器将控制权传递给过滤器链中的下一个过滤器或者资源。FilterConfig对象将由容器提供给过滤器,以允许访问该过滤器的初始化数据。

14.2.1 编写实现类的程序

下面是一个简单的Servlet过滤器的例子,其中将获取一个Web服务器给客户提供服务需要的时间,也就是响应时间。

【本节示例参考:资料光盘\源代码\第14章\14-2\Servlet.java】

【实例14-1】这是一个实现类的程序,主要是使用过滤器处理请求,在处理时可能要耗费很多时间。

程序14-1 Servlet.java

01 package cn.ac.ict.Filter;

02 import java.io.IOException;

03 import java.io.PrintWriter;

04 import java.io.StringWriter;

05 import java.util.Date;

06 import javax.servlet.*;

07 public class ResponseTimeFilter implements Filter {

08 private FilterConfig filterConfig = null;

09 public void init(FilterConfig config) throws ServletException {//初始化函数

10 this.filterConfig = config;

11 }

12 public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) thro ws IOException, ServletException {

13 Date startTime, endTime;

14 double totalTime;

15 startTime = new Date();

16 StringWriter sw = new StringWriter();

17 PrintWriter writer = new PrintWriter(sw);

18 writer.println();

19 writer.println("ResponseTimeFilter Before call chain.doFilter");

20 chain.doFilter(request, response); //将请求转发给过滤器链中下一个资源

21 //下面是资源的响应时间

22 //下面计算开始时间和结束时间的差,也就是响应时间

23 endTime = new Date();

24 totalTime = endTime.getTime() - startTime.getTime();

25 totalTime = totalTime / 1000; //将毫秒时间转换为以秒为单位

26 writer.println("ResponseTimeFilter Before call chain.doFilter");

27 writer.println("===============");

28 writer.println("Total elapsed time is: " + totalTime + " seconds." );

29 writer.println("===============");

30 filterConfig.getServletContext().log(sw.getBuffer().toString()); //将信息输出到日志中

31 writer.flush();

32 }

33 public void destroy() {

34 this.filterConfig = null;

35 }

36 public FilterConfig getFilterConfig() {

37 return null;

38 }

39 public void setFilterConfig(FilterConfig config) {

40 }

41 }

【深入学习】当容器第一次加载该过滤器时,init()方法将被调用。该类在这个方法中包含了一个指向FilterConfig对象的引用。过滤器的大多数时间都消耗在执行过滤操作上,第20行中的doFilter()方法被容器调用,同时传入分别指向这个请求/响应链中的ServletRequest、ServletResponse和FilterChain对象的引用。然后过滤器就有机会处理请求,将处理任务传递给链中的下一个资源(通过调用FilterChain对象引用上的doFilter()方法),之后在处理控制权返回该过滤器时处理响应。

14.2.2 配置发布Servlet过滤器

发布Servlet过滤器时,必须在web.xml文件中加入元素,其中,元素用来定义一个过滤器,在本例中使用了如下代码:

Request Response Time

cn.ac.ict.Filter.ResponseTimeFilter

以上代码中,指定过滤器的名字,指定过滤器的完整类名,也可以在元素中内嵌元素来为过滤器提供初始化参数。

元素用于将过滤器和URL关联,本例中的代码如下:

Request Response Time

/ShowCounter.jsp

当客户请求的URL和指定的URL相匹配时,就会触发ResponseTi meFilter过滤器,这里的元素的值和元素中的值应该相同。

说明:如果希望Servlet过滤器能过滤所有的客户请求,可以将的值设置为/*。

准备好上面的文件后,按照如下步骤即可发布这个Servlet过滤器。

(1)编译ResponseTimeFilter类,编译时,需要将Java Servlet API的包se rvlet-api.jar放到类路径中,编译后的类放到本章Web应用的WEB-INF\class es目录下,并且目录结构要与包的结构一致。

(2)在web.xml文件中按照上面说明的方法配置这个过滤器。

(3)为了观察这个过滤器的日志,应确保在server.xml文件的localhost对应的host元素中配置了如下的logger元素:

directory="logs" prefix="localhost_log." suffix=".txt"

timestamp="true"/>

以上内容在默认情况下都是配置好的。

(4)启动Tomcat,在浏览器地址栏中输入http://localhost:8080/14/ShowCo unter.jsp,这时在\logs\ localhost_log.2009-05-24.txt文件中会生成如下的日志信息:

2009-05-26 23:03:42 StandardContext[/15]

ResponseTimeFilter Before call chain.doFilter

ResponseTimeFilter Before call chain.doFilter

===============

Total elapsed time is: 0.0 seconds.

===============

说明:本例中访问的ShowCounter.jsp页面在本章的监听器部分介绍,如果读者不想跳跃着查看这个文件,可以尝试修改过滤器关联的URL,使得它可以过滤读者指定的资源。

14.3 ServletRequest和ServletResponse的包装类

Servlet过滤器能够对客户的请求和响应进行包装,并根据自定义的行为对请求和响应进行修改,这就是通过使用ServletRequest和ServletResponse的包装类——HttpServletRequestWrapper和HttpServletResponseWrapper类来实现的。这两个类提供了所有request和response方法的默认实现,并且默认地代

理了所有对原有request和response的调用。这也意味着,要改变一个方法的行为只需要从封装类继承并重新实现一个方法即可。

● ServletRequestWrapper类:是ServletRequest的包装类,实现了ServletRequest接口,并对其方法提供了默认实现。

● ServletResp onseWrapper类:是ServletResponse的包装类,实现了ServletResponse接口,并对其方法提供了默认实现。

14.4 用Servlet过滤器过滤文本信息

在14.3节中简单介绍了ServletRequest和ServletResponse的包装类,它们在过滤器设计中会经常被用到。本节将介绍一个使用HttpServletResponseWrappe r类的例子,客户输入的内容会被检查,如果存在不允许出现的信息,就会被过滤掉,而被替换成其他的字符。在这个例子中,客户输入用户名并发布自己的留言信息,然后提交给服务器,服务器再将这些信息反馈给客户。如果客户的留言中有指定的字符串,就会调用过滤器将这个字符串替换掉。

14.4.1 输出流管理类

在Servlet输出时可以选择不同的输出流,将它们集合在一起,即可很容易地对这些流进行管理,使用时也会更方便。如何使用输出流管理类,可以看下面的代码。

【本节示例参考:资料光盘\源代码\第14章\14-4\ByteArrayPrintWriter.java】

【实例14-2】将输出流进行方法的实例化,方便以后程序的调用。

程序14-2 ByteArrayPrintWriter.java

01 package cn.ac.ict;

02 import java.io.ByteArrayOutputStream;

03 import java.io.PrintWriter;

04 import javax.servlet.ServletOutputStream;

05 public class ByteArrayPrintWriter {

06 private ByteArrayOutputStream baos = new ByteArrayOutputStream();

07 private PrintWriter pw = new PrintWriter(baos);

08 private ByteArrayOutputStream sos = new ByteArrayOutputStream();

09 public ByteArrayPrintWriter(ByteArrayOutputStream aos) {

10 this.baos = aos;

11 }

12 //一个为空的构造方法

13 public ByteArrayPrintWriter() {

14 }

15 //使用字符输出流时调用获取输出流

16 public PrintWriter getWriter() {

17 return pw;

18 }

19 //使用字节输出流时调用获取输出流

20 public ByteArrayOutputStream getStream() {

21 return sos;

22 }

23 byte[] toByteArray() {

24 return baos.toByteArray();

25 }

26 }

【深入学习】在第6、7、8行共定义了3个对象,一个是用字符流输出的,一个是用字节流输出的,但是它们最终都套接到类型为ByteArrayOutputStream的对象上,这个对象为它们提供了原始输出数据。

14.4.2 编写Servlet过滤器

前面编写了需要使用的辅助类,接下来即可开发进行实际过滤功能的文本过滤器。

【本节示例参考:资料光盘\源代码\第14章\14-4\TextModifierFilter.java】

【实例14-3】如何实现文本过滤器,代码(TextModifierFilter.java)如下:

程序14-3 TextModifierFilter.java

01 package cn.ac.ict;

02 import java.io.IOException;

03 import java.io.PrintWriter;

04 import java.util.Locale;

05 import javax.servlet.Filter;

06 import javax.servlet.FilterChain;

07 import javax.servlet.FilterConfig;

08 import javax.servlet.ServletException;

09 import javax.servlet.ServletOutputStream;

10 import javax.servlet.ServletRequest;

11 import javax.servlet.ServletResponse;

12 import javax.servlet.http.HttpServletRequest;

13 import javax.servlet.http.HttpServletResponse;

14 import com.sun.xml.internal.ws.util.ByteArrayBuffer;

15 public class TextModifierFilter implements Filter, ServletResponse {

16 private FilterConfig filterConfig;

17 private String searchStr;

18 private String replaceStr;

19 public void init(FilterConfig config) throws ServletException {

20 this.filterConfig = config;

21 //初始化要查找的字符串和替换后的字符串的值

22 this.searchStr = "search";

23 this.replaceStr = "replace";

24 }

25 public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) thro ws IOException, ServletException {

26 HttpServletRequest hsr = (HttpServletRequest)request;

27 final HttpServletResponse resp = (HttpServletResponse)response;

28 final ByteArrayBuffer pw = new ByteArrayBuffer();

29 //构造响应包装类

30 TextModifierFilter tmrw = new TextModifierFilter();

31 filterConfig.getServletContext().log("TextModifierFilter:Before call chain.doFilter");

32 //将请求转发给Servlet,并获取响应

33 chain.doFilter(request,tmrw);

34 //下面的代码检查响应并对需要替换的字符进行替换

35 //将响应转化为byte数组

36 byte[] bytes = pw.toByteArray();

37 if (bytes == null || (bytes.length == 0)) {

38 filterConfig.getServletContext().log("No content!");

39 }

40 //将其转换为字符串便于查找

41 String content = new String(bytes);

42 String searchcontent = (new String(bytes)).toLowerCase();

43 //查找指定字符出现的第一个位置

44 int endPos = searchcontent.indexOf(this.searchStr);

45 String returnStr;

46 //响应中包含这样一个字符串

47 if(endPos!=-1){

48 String front_part_Str = content.substring(0,endPos);

49 returnStr=front_part_Str + ""+this.replaceStr+""+content.subst ring (endPos + this.searchStr.length());

50 }else{

51 //响应中不包含这样一个字符串

52 returnStr = content;

53 }

54 resp.setContentLength(returnStr.length());

55 resp.getOutputStream().write(returnStr.getBytes());

56 filterConfig.getServletContext().log("TextModifierFilter:After call chain.doFilter");

57 }

58 public void destroy() {

59 this.filterConfig = null;

60 }

61 public FilterConfig getFilterConfig() {

62 return null;

63 }

64 public void setFilterConfig(FilterConfig config) {

65 }

66 public void flushBuffer() throws IOException {

67 }

68 public int getBufferSize() {

69 return 0;

70 }

71 public String getCharacterEncoding() {

72 return null;

73 }

74 public String getContentType() {

75 return null;

76 }

77 public Locale getLocale() {

78 return null;

79 }

80 public ServletOutputStream getOutputStream() throws IOException {

81 return null;

82 }

83 public PrintWriter getWriter() throws IOException {

84 return null;

85 }

86 public boolean isCommitted() {

87 return false;

88 }

89 public void reset() {

90 }

91 public void resetBuffer() {

92 }

93 public void setBufferSize(int arg0) {

94 }

95 public void setCharacterEncoding(String arg0) {

96 }

97 public void setContentLength(int arg0) {

98 }

99 public void setContentType(String arg0) {

100 }

101 public void setLocale(Locale arg0) {

102 }

103 }

【深入学习】上述代码的主要功能集中在第25行中的doFilter()方法中,在这个方法中构造了一个响应包装器。当调用chain.doFilter(request,tmrw)方法时,Servlet的响应都已经在响应包装器对象tmrw中了,之后即可调用相关的方法获取响应的内容,并进行适当的处理。

14.4.3 编写JSP和Servlet文件

前面已经开发完成了一个完整的文本过滤器的程序,这里将接着开发演示过滤器工作过程的JSP和Servlet文件。

【本节示例参考:资料光盘\源代码\第14章\14-4\textModifier.jsp】

【实例14-4】实现一个过滤器的程序,查看它的工作过程。首先来看利用JSP文件实现的代码。

程序14-4 textModifier.jsp

01 <%@ page language="java" pageEncoding="GB2312"%>

02

03

04 User Message

05

12

13

14

15

16

17 User Message

18

19

20

21

22

23

26

29

30

31

34

37

38

39

42

45

46

24 UserName

25

27

28

32 Message:

33

35

36

40

41

43

44

47

48

49

下面是用户提交输入留言信息的响应Servlet,其代码如下:

TextModiferServlet.java

01 package cn.ac.ict;

02 import java.io.IOException;

03 import javax.servlet.ServletException;

04 import javax.servlet.ServletOutputStream;

05 import javax.servlet.http.HttpServlet;

06 import javax.servlet.http.HttpServletRequest;

07 import javax.servlet.http.HttpServletResponse;

08 public class TextModiferServlet extends HttpServlet {

09 protected void doGet(HttpServletRequest request,HttpServletResponse response) throws Servlet Exception, IOException {

10 //设置响应的类型

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

12 ServletOutputStream out = response.getOutputStream();

13 //获取请求参数

14 String user = request.getParameter("username");

15 String content = request.getParameter("content");

16 String outstr = " ";

17 if (user != null) {

18 user = new String(user.getBytes("ISO8859-1"), "GB2312");

19 }

20 if (content != null) {

21 content = new String(content.getBytes("ISO8859-1"), "GB2312");

22 }

23 if ((user != null) && (content != null)) {

24 outstr = "User[" + user + "] say: '" + content + "'";

25 }

26 out.println("\r\n");

27 out

28 .println("\r\n");

29 out.println("\r\n");

30 out.println("\r\n");

31 out.println("User Message\r\n");

32 out.println("\r\n");

39 out.println("\r\n");

40 out.println("\r\n");

41 out.println("

\r\n");

42 out.println("

\r\n");

43 out.println("

User M essage \r\n");

44 out.println("

\r\n");

45 out.println("

\r\n");

46 out.println("

\r\n");

47 out.println("

\r\n");

49 out.println("

\r\n");

50 out.println("

\r\n");

51 out.println("

" + outstr + "
UserName

d>\r\n");

48 out.println("

Message: