过滤器 (Filter)

过滤器实际上就是对 web 资源进行拦截,做一些处理后再交给下一个过滤器或 servlet 处理
通常都是用来拦截 request 进行处理的,也可以对返回的 response 进行拦截处理

大概流程图如下

Java Web之过滤器(Filter) - 图1

应用场景

  • 自动登录
  • 统一设置编码格式
  • 访问权限控制
  • 敏感字符过滤等

创建 Filter

在 Servlet 中我们一般都会对 request 和 response 中的字符集编码进行配置,如果 Servlet 过多字符集编码发生变化时修改起码会很麻烦,这些通用的字符集编码配置等工作我们可以放到 Filter 中来实现。
下面我们来创建一个处理字符集编码的 Filter:

  1. package filter;
  2. import javax.servlet.*;
  3. import javax.servlet.annotation.WebFilter;
  4. import java.io.IOException;
  5. @WebFilter(filterName = "CharsetFilter")
  6. public class CharsetFilter implements Filter {
  7. public void destroy() {
  8. /*销毁时调用*/
  9. }
  10. public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
  11. /*过滤方法 主要是对request和response进行一些处理,然后交给下一个过滤器或Servlet处理*/
  12. chain.doFilter(req, resp);//交给下一个过滤器或servlet处理
  13. }
  14. public void init(FilterConfig config) throws ServletException {
  15. /*初始化方法 接收一个FilterConfig类型的参数 该参数是对Filter的一些配置*/
  16. }
  17. }

配置 Filter

可配置的属性有这些

Java Web之过滤器(Filter) - 图2

常用配置项
urlPatterns
配置要拦截的资源

  1. 以指定资源匹配。例如"/index.jsp"
  2. 以目录匹配。例如"/servlet/*"
  3. 以后缀名匹配,例如"*.jsp"
  4. 通配符,拦截所有 web 资源。"/*"

initParams
配置初始化参数,跟 Servlet 配置一样

例如

  1. initParams = {
  2. @WebInitParam(name = "key",value = "value")
  3. }

dispatcherTypes
配置拦截的类型,可配置多个。默认为 DispatcherType.REQUEST
例如

  1. dispatcherTypes = {DispatcherType.ASYNC,DispatcherType.ERROR}

其中 DispatcherType 是个枚举类型,有下面几个值

  1. FORWARD,//转发的
  2. INCLUDE,//包含在页面的
  3. REQUEST,//请求的
  4. ASYNC,//异步的
  5. ERROR;//出错的

下面我们来对 CharsetFilter 代码进行一下修改

  1. package filter;
  2. import javax.servlet.*;
  3. import javax.servlet.annotation.WebFilter;
  4. import javax.servlet.annotation.WebInitParam;
  5. import java.io.IOException;
  6. @WebFilter(filterName = "CharsetFilter",
  7. urlPatterns = "/*",/*通配符(*)表示对所有的web资源进行拦截*/
  8. initParams = {
  9. @WebInitParam(name = "charset", value = "utf-8")/*这里可以放一些初始化的参数*/
  10. })
  11. public class CharsetFilter implements Filter {
  12. private String filterName;
  13. private String charset;
  14. public void destroy() {
  15. /*销毁时调用*/
  16. System.out.println(filterName + "销毁");
  17. }
  18. public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
  19. /*过滤方法 主要是对request和response进行一些处理,然后交给下一个过滤器或Servlet处理*/
  20. System.out.println(filterName + "doFilter()");
  21. req.setCharacterEncoding(charset);
  22. resp.setCharacterEncoding(charset);
  23. chain.doFilter(req, resp);
  24. }
  25. public void init(FilterConfig config) throws ServletException {
  26. /*初始化方法 接收一个FilterConfig类型的参数 该参数是对Filter的一些配置*/
  27. filterName = config.getFilterName();
  28. charset = config.getInitParameter("charset");
  29. System.out.println("过滤器名称:" + filterName);
  30. System.out.println("字符集编码:" + charset);
  31. }
  32. }

这样一个简单的字符集编码处理的过滤器就完成了
我们看看执行打印的结果
Java Web之过滤器(Filter) - 图3

需要注意的是
过滤器是在服务器启动时就会创建的,只会创建一个实例,常驻内存,也就是说服务器一启动就会执行 Filter 的 init(FilterConfig config) 方法.
当 Filter 被移除或服务器正常关闭时,会执行 destroy 方法

多个 Filter 的执行顺序

在我们的请求到达 Servle 之间是可以经过多个 Filter 的,一般来说,建议 Filter 之间不要有关联,各自处理各自的逻辑即可。这样,我们也无需关心执行顺序问题。
如果一定要确保执行顺序,就要对配置进行修改了,执行顺序如下

  1. 在 web.xml 中,filter 执行顺序跟<filter-mapping>的顺序有关,先声明的先执行
  2. 使用注解配置@WebFilter的话,filter 的执行顺序跟名称的字母顺序有关,例如 AFilter 会比 BFilter 先执行
  3. 如果既有在 web.xml 中声明的 Filter,也有通过注解配置@WebFilter的 Filter,那么会优先执行 web.xml 中配置的 Filter

我们写个小例子看一下

新建 3 个 Filter,加上之前的 CharsetFilter 一共四个

Java Web之过滤器(Filter) - 图4

其中 CharsetFilterABFilter 是通过注解声明的

CharsetFilter 注解配置

  1. @WebFilter(filterName = "CharsetFilter",
  2. urlPatterns = "/*",/*通配符(*)表示对所有的web资源进行拦截*/
  3. initParams = {
  4. @WebInitParam(name = "charset", value = "utf-8")/*这里可以放一些初始化的参数*/
  5. })

ABFilter

  1. @WebFilter(filterName = "ABFilter",urlPatterns = "/*")

AFilterBFilter 是在 web.xml 配置的。
执行顺序跟<filter>的顺序无关
<filter-mapping>的顺序才决定执行顺序

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  5. version="4.0">
  6. <filter>
  7. <filter-name>AFilter</filter-name>
  8. <filter-class>filter.AFilter</filter-class>
  9. </filter>
  10. <filter>
  11. <filter-name>BFilter</filter-name>
  12. <filter-class>filter.BFilter</filter-class>
  13. </filter>
  14. <!--这里BFilter在AFilter之前-->
  15. <filter-mapping>
  16. <filter-name>BFilter</filter-name>
  17. <url-pattern>/filter.jsp</url-pattern>
  18. </filter-mapping>
  19. <filter-mapping>
  20. <filter-name>AFilter</filter-name>
  21. <url-pattern>/filter.jsp</url-pattern>
  22. </filter-mapping>
  23. </web-app>

每个 Filter 添加了打印语句,如下
以 ABFilter 为例

  1. package filter;
  2. import javax.servlet.*;
  3. import javax.servlet.annotation.WebFilter;
  4. import java.io.IOException;
  5. @WebFilter(filterName = "ABFilter",urlPatterns = "/*")
  6. public class ABFilter implements Filter {
  7. private String filterName;
  8. public void destroy() {
  9. }
  10. public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
  11. System.out.println(filterName + " doFilter()");
  12. chain.doFilter(req, resp);
  13. }
  14. public void init(FilterConfig config) throws ServletException {
  15. filterName= config.getFilterName();
  16. System.out.println("过滤器名称:" + filterName +" init");
  17. }
  18. }

下面我们来访问 filter.jsp 看看打印结果

Java Web之过滤器(Filter) - 图5

可以看到,执行结果符合预期。
BFilter 和 AFilter 是在 web.xml 中声明的,且 BFilter 的<filter-mapping>在前,故 BFilter 在 AFilter 之前执行。
ABFilter 和 CharsetFilter 是通过注解声明的,故他俩在 BFilter 和 AFilter 之后执行,但是 ABFilter 的名称以 A 开头,故在 CharsetFilter 之前执行

Java Web之过滤器(Filter) - 图6

访问权限控制小例子

下面我们写一个访问控制权限控制的小例子。
我们在浏览一些网站经常有这个情况,没有登录时是不允许我们访其主页的,只有登录过后才能访问。
下面我们就用 Filter 简单实现一下。

需求分析

  1. 登录时将登录的账号密码保存到 cookie 中,下次访问时携带账号和密码,过滤器中进行校验
  2. 用户没有登录直接访问主页时,要跳转到登录页面
  3. 登录过滤器不对登录页面进行过滤

我们先来看一下项目结构

Java Web之过滤器(Filter) - 图7

这里主要看一下 LoginFilter 的代码

我们在 LoginFilter 中对非登录页面的其他 jsp 都会进行过滤,判断 cookie 中是否携带了 account 和 pwd。
如果有这两个数据表示之前登录过,那么对数据进行校验,正确的话就进行下一个操作。
否则的话,跳转到登录界面

  1. package filter;
  2. import javax.servlet.*;
  3. import javax.servlet.annotation.WebFilter;
  4. import javax.servlet.http.Cookie;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. @WebFilter(filterName = "LoginFilter", urlPatterns = "*.jsp", dispatcherTypes = {})
  9. public class LoginFilter implements Filter {
  10. public void destroy() {
  11. }
  12. public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
  13. System.out.println("LoginFilter doFilter");
  14. HttpServletRequest request = (HttpServletRequest) req;
  15. HttpServletResponse response = (HttpServletResponse) resp;
  16. String url = request.getRequestURI();
  17. System.out.println("请求的url:" + url);
  18. /*登录页面不需要过滤*/
  19. int idx = url.lastIndexOf("/");
  20. String endWith = url.substring(idx + 1);
  21. if (!endWith.equals("login.jsp")) {
  22. /*不是登录页面 进行拦截处理*/
  23. System.out.println("不是登录页面,进行拦截处理");
  24. if (!isLogin(request)) {
  25. System.out.println("没有登录过或者账号密码错误,跳转到登录界面");
  26. response.sendRedirect("login.jsp");
  27. } else {
  28. System.out.println("已经登录,进行下一步");
  29. chain.doFilter(req, resp);
  30. }
  31. } else {
  32. System.out.println("是登录页面,不进行拦截处理");
  33. chain.doFilter(req, resp);
  34. }
  35. }
  36. private boolean isLogin(HttpServletRequest request) {
  37. Cookie[] cookies = request.getCookies();
  38. String account = "";
  39. String pwd = "";
  40. if (cookies != null && cookies.length > 0) {
  41. for (Cookie cookie : cookies) {
  42. if (cookie.getName().equals("account")) {
  43. account = cookie.getValue();
  44. } else if (cookie.getName().equals("pwd")) {
  45. pwd = cookie.getValue();
  46. }
  47. }
  48. }
  49. if (account.equals("") || pwd.equals("")) {
  50. return false;
  51. } else if (account.equals("yzq") && pwd.equals("123")) {
  52. return true;
  53. }
  54. return false;
  55. }
  56. public void init(FilterConfig config) throws ServletException {
  57. System.out.println("LoginFilter init");
  58. }
  59. }

执行效果

Java Web之过滤器(Filter) - 图8

可以看到,我们在没有登录的情况下直接去访问 index.jsp 页面时会自动跳转到登录页面,在登录成功后,再次直接访问 index 页面则可以访问。