在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以针对某一个 URL 进行拦截。如果多个 Filter 程序都对同一个 URL 进行拦截,那么这些 Filter 就会组成一个Filter 链(也称过滤器链)。

Filter 链用 FilterChain 对象表示,FilterChain 对象中有一个 doFilter() 方法,该方法的作用是让 Filter 链上的当前过滤器放行,使请求进入下一个 Filter。

Filter 链的拦截过程如图 1 所示。

图 1 Filter链

在图 1 中,当浏览器访问 Web 服务器中的资源时,需要经过两个过滤器 Filter1 和 Filter2。首先 Filter1 会对这个请求进行拦截,在 Filter1 中处理完请求后,通过调用 Filter1 的 doFilter() 方法将请求传递给 Filter2,Filter2 处理用户请求后同样调用 doFilter() 方法,最终将请求发送给目标资源。当 Web 服务器对这个请求做出响应时,也会被过滤器拦截,但这个拦截顺序与之前相反,最终将响应结果发送给客户端浏览器。

为了便于读者理解 Filter 链的拦截过程以及掌握 Filter 链的使用,下面通过案例演示如何使用 Filter 链拦截 MyServlet 的同一个请求。

1)创建过滤器

在 filterDemo01 项目的 com.mengma.filter 包中新建两个过滤器 MyFilter01 和 MyFilter02,如 MyFilter01 和 MyFilter02 所示。

① MyFilter01

  1. package com.mengma.filter;
  2. import java.io.IOException;
  3. import java.io.PrintWriter;
  4. import javax.servlet.Filter;
  5. import javax.servlet.FilterChain;
  6. import javax.servlet.FilterConfig;
  7. import javax.servlet.ServletException;
  8. import javax.servlet.ServletRequest;
  9. import javax.servlet.ServletResponse;
  10. public class MyFilter01 implements Filter {
  11. public void init(FilterConfig fConfig) throws ServletException {
  12. // 过滤器对象在初始化时调用,可以配置一些初始化参数
  13. }
  14. public void doFilter(ServletRequest request, ServletResponse response,
  15. FilterChain chain) throws IOException, ServletException {
  16. // 用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,则该方法会被调用
  17. PrintWriter out = response.getWriter();
  18. out.write("MyFilter01
    ");
  19. chain.doFilter(request, response);
  20. }
  21. public void destroy() {
  22. // 过滤器对象在销毁时自动调用,释放资源
  23. }
  24. }

② MyFilter02

  1. package com.mengma.filter;
  2. import java.io.IOException;
  3. import java.io.PrintWriter;
  4. import javax.servlet.Filter;
  5. import javax.servlet.FilterChain;
  6. import javax.servlet.FilterConfig;
  7. import javax.servlet.ServletException;
  8. import javax.servlet.ServletRequest;
  9. import javax.servlet.ServletResponse;
  10. public class MyFilter02 implements Filter {
  11. public void init(FilterConfig fConfig) throws ServletException {
  12. // 过滤器对象在初始化时调用,可以配置一些初始化参数
  13. }
  14. public void doFilter(ServletRequest request, ServletResponse response,
  15. FilterChain chain) throws IOException, ServletException {
  16. // 用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,则该方法会被调用
  17. PrintWriter out = response.getWriter();
  18. out.write("MyFilter02 Before
    ");
  19. chain.doFilter(request, response);
  20. out.write("
    MyFilter02 After
    ");
  21. }
  22. public void destroy() {
  23. // 过滤器对象在销毁时自动调用,释放资源
  24. }
  25. }

2)修改 web.xml

为了防止其他过滤器影响此次 Filter 链的演示效果,需要先将 web.xml 文件中的其他过滤器的配置信息注释掉,然后将 MyFilter01 和 MyFilter02 过滤器的映射信息配置在 MyServlet 配置信息前面,具体如下所示。

  1. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  3. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  4. version="3.0">
  5. MyFilter01
  6. com.mengma.filter.MyFilter01
  7. MyFilter01
  8. /MyServlet
  9. MyFilter02
  10. com.mengma.filter.MyFilter02
  11. MyFilter02
  12. /MyServlet
  13. MyServlet
  14. com.mengma.filter.MyServlet
  15. MyServlet
  16. /MyServlet

3)运行项目并查看结果

启动 Tomcat 服务器,在浏览器的地址栏中输入 http://localhost:8080/filterDemo01/MyServlet,此时,浏览器窗口中的显示结果如图 2 所示。

图 2 运行结果

从图 2 中可以看出,MyServlet 首先被 MyFilter01 拦截了,显示出 MyFilter01 中的内容,然后被 MyFilter02 拦截,直到 MyServlet 被 MyFilter02 放行后,浏览器才显示出 MyServlet 中的输出内容。

需要注意的是,Filter 链中各个 Filter 的拦截顺序与它们在 web.xml 文件中元素的映射顺序一致,由于 MyFilter01 的元素位于 MyFilter02 的元素前面,因此,用户的访问请求首先会被 MyFilter01 拦截,然后再被 MyFilter02 拦截。