一键搞定内网穿透 联行号查询|开户行查询 在线工具箱 藏经阁
当前位置:首页 / 互联网与IT技术 / 正文
实现FilterChain

上一节我们实现了ServletContext,并且能够管理所有的Servlet组件。本节我们继续增加对Filter组件的支持。

Filter是Servlet规范中的一个重要组件,它的作用是在HTTP请求到达Servlet之前进行预处理。它可以被一个或多个Filter按照一定的顺序组成一个处理链(FilterChain),用来处理一些公共逻辑,比如打印日志、登录检查等。

Filter还可以有针对性地拦截或者放行HTTP请求,本质上一个FilterChain就是一个责任链模式。在Servlet容器中,处理流程如下:

 ┌─────────────────┐
 │ ServletContext │
 │ ┌ ─ ─ ─ ─ ─ ─ ┐ │
 │  FilterChain  │
 │ │ ┌─────────┐ │ │
──┼──▶│ Filter │  │
 │ │ └─────────┘ │ │
 │    │    │
 │ │   ▼   │ │
 │  ┌─────────┐  │
 │ │ │ Filter │ │ │
 │  └─────────┘  │
 │ │   │   │ │
 │    ▼    │
 │ │ ┌─────────┐ │ │
 │  │ Filter │  │
 │ │ └─────────┘ │ │
 │ ─ ─ ─ ┬ ─ ─ ─ │
 │    ▼    │
 │  ┌─────────┐  │
 │  │ Servlet │  │
 │  └─────────┘  │
 └─────────────────┘

这里有几点需要注意:

  1. 最终处理请求的Servlet是根据请求路径选择的;
  2. Filter链上的Filter是根据请求路径匹配的,可能匹配0个或多个Filter;
  3. 匹配的Filter将组成FilterChain进行调用。

下面,我们首先将Filter纳入ServletContext中管理。和ServletMapping类似,先定义FilterMapping,它包含一个Filter实例,以及将映射路径编译为正则表达式:

public class FilterMapping {
  final Pattern pattern; // 编译后的正则表达式
  final Filter filter;

  public FilterMapping(String urlPattern, Filter filter) {
    this.pattern = buildPattern(urlPattern); // 编译为正则表达式
    this.filter = filter;
  }
}

接着,根据Servlet规范,我们需要提供addFilter()动态添加一个Filter,并且返回FilterRegistration.Dynamic,所以需要在ServletContext中实现相关方法:

public class ServletContextImpl implements ServletContext {
  Map<String, FilterRegistrationImpl> filterRegistrations = new HashMap<>();
  Map<String, Filter> nameToFilters = new HashMap<>();
  List<FilterMapping> filterMappings = new ArrayList<>();

  // 根据Class Name添加Filter:
  @Override
  public FilterRegistration.Dynamic addFilter(String name, String className) {
    return addFilter(name, Class.forName(className));
  }

  // 根据Class添加Filter:
  @Override
  public FilterRegistration.Dynamic addFilter(String name, Class<? extends Filter> clazz) {
    return addFilter(name, clazz.newInstance());
  }

  // 根据Filter实例添加Filter:
  @Override
  public FilterRegistration.Dynamic addFilter(String name, Filter filter) {
    var registration = new FilterRegistrationImpl(this, name, filter);
    this.filterRegistrations.put(name, registration);
    return registration;
  }
  ...
}

再添加一个initFilters()方法用于向容器添加Filter

public class ServletContextImpl implements ServletContext {
  ...
  public void initFilters(List<Class<?>> filterClasses) {
    for (Class<?> c : filterClasses) {
      // 获取@WebFilter注解:
      WebFilter wf = c.getAnnotation(WebFilter.class);
      // 添加Filter:
      FilterRegistration.Dynamic registration = this.addFilter(AnnoUtils.getFilterName(clazz), clazz);
      // 添加URL映射:
      registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, AnnoUtils.getFilterUrlPatterns(clazz));
      // 设置初始化参数:
      registration.setInitParameters(AnnoUtils.getFilterInitParams(clazz));
    }
    for (String name : this.filterRegistrations.keySet()) {
      // 依次处理每个FilterRegistration.Dynamic:
      var registration = this.filterRegistrations.get(name);
      // 调用Filter.init()方法:
      registration.filter.init(registration.getFilterConfig());
      this.nameToFilters.put(name, registration.filter);
      // 将Filter定义的每个URL映射编译为正则表达式:
      for (String urlPattern : registration.getUrlPatternMappings()) {
        this.filterMappings.add(new FilterMapping(urlPattern, registration.filter));
      }
    }
  }
  ...
}

这样,我们就完成了对Filter组件的管理。

下一步,是改造process()方法,把原来直接把请求扔给Servlet处理,改成先匹配Filter,处理后再扔给最终的Servlet

public class ServletContextImpl implements ServletContext {
  ...
  public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    // 获取请求路径:
    String path = request.getRequestURI();
    // 查找Servlet:
    Servlet servlet = null;
    for (ServletMapping mapping : this.servletMappings) {
      if (mapping.matches(path)) {
        servlet = mapping.servlet;
        break;
      }
    }
    if (servlet == null) {
      // 404错误:
      PrintWriter pw = response.getWriter();
      pw.write("<h1>404 Not Found</h1><p>No mapping for URL: " + path + "</p>");
      pw.close();
      return;
    }
    // 查找Filter:
    List<Filter> enabledFilters = new ArrayList<>();
    for (FilterMapping mapping : this.filterMappings) {
      if (mapping.matches(path)) {
        enabledFilters.add(mapping.filter);
      }
    }
    Filter[] filters = enabledFilters.toArray(Filter[]::new);
    // 构造FilterChain实例:
    FilterChain chain = new FilterChainImpl(filters, servlet);
    // 由FilterChain处理:
    chain.doFilter(request, response);
  }
  ...
}

注意上述FilterChain不仅包含一个Filter[]数组,还包含一个Servlet,这样我们调用chain.doFilter()时,在FilterChain中最后一个处理请求的就是Servlet,这样设计可以简化我们实现FilterChain的代码:

public class FilterChainImpl implements FilterChain {
  final Filter[] filters;
  final Servlet servlet;
  final int total; // Filter总数量
  int index = 0; // 下一个要处理的Filter[index]

  public FilterChainImpl(Filter[] filters, Servlet servlet) {
    this.filters = filters;
    this.servlet = servlet;
    this.total = filters.length;
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    if (index < total) {
      int current = index;
      index++;
      // 调用下一个Filter处理:
      filters[current].doFilter(request, response, this);
    } else {
      // 调用Servlet处理:
      servlet.service(request, response);
    }
  }
}

注意FilterChain是一个递归调用,因为在执行Filter.doFilter()时,需要把FilterChain自身传进去,在执行Filter.doFilter()之前,就要把index调整到正确的值。

我们编写两个测试用的Filter:

  • LogFilter:匹配/*,打印请求方法、路径等信息;
  • HelloFilter:匹配/hello,根据请求参数决定放行还是返回403错误。

在初始化ServletContextImpl时将Filter加进去,先测试http://localhost:8080/

观察后台输出,LogFilter应该起作用:

16:48:00.304 [HTTP-Dispatcher] INFO c.i.j.engine.filter.LogFilter -- GET: /

再测试http://localhost:8080/hello?name=Bob

观察后台输出,HelloFilterLogFilter应该起作用:

16:49:31.409 [HTTP-Dispatcher] INFO c.i.j.engine.filter.HelloFilter -- Check parameter name = Bob
16:49:31.409 [HTTP-Dispatcher] INFO c.i.j.engine.filter.LogFilter -- GET: /hello

最后测试http://localhost:8080/hello?name=Jim

可以看到,HelloFilter拦截了请求,返回403错误,最终的HelloServlet并没有处理该请求。

现在,我们就成功地在ServletContext中实现了对Filter的管理,以及根据每个请求,构造对应的FilterChain来处理请求。目前还有几个小问题:

一是和Servlet一样,Filter本身应该是Web App开发人员实现,而不是由服务器实现。我们在在服务器中写死了两个Filter,这个问题后续解决;

二是Servlet规范并没有规定多个Filter应该如何排序,我们在实现时也没有对Filter进行排序。如果要按固定顺序给Filter排序,从Servlet规范来说怎么排序都可以,通常是按@WebFilter定义的filterName进行排序,Spring Boot提供的一个FilterRegistrationBean允许开发人员自己定义Filter的顺序。

参考源码

可以从GitHub或Gitee下载源码。

GitHub

小结

实现FilterChain时,要首先在ServletContext内完成所有Filter的初始化和映射,然后,根据请求路径匹配所有合适的Filter和唯一的Servlet,构造FilterChain并处理请求。

转载