Skip to content

过滤器

案例目标

  • 利用过滤器技术,实现审计功能
  • 利用过滤器技术,实现登录权限校验功能
  • 利用过滤器技术,实现全局乱码统一处理功能
  • 利用过滤器技术,实现脏词过滤功能

过滤器入门

过滤器概述

Water-Purifier-Front

Water-Purifier

Filter-In-Life

1.1.1过滤器的作用【重点】

  • 过滤 HTTP 交互,即请求和响应
  • 对 HTTP 请求和响应进行拦截
  • 对 HTTP 请求和响应进行放行
  • 对 HTTP 请求和响应进行增强

过滤器的基本原理

Filter-Case

过滤器的使用场景

  • 脏词过滤
  • 审计 - 对 Web 应用的工作效率进行监控
  • 登录权限校验
  • 全站乱码处理

相关API概要【重点】

Filter-Class-Hierachy

过滤器快速入门【重点】

步骤

  • 新建普通的 Java 类,实现 Filter 接口。重写 doFilter 方法
  • 配置 Filter
    • 类路径
    • 映射路径

实现

  • FireEmployeeServlet.java

    java
    package com.futureweaver.web;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    /**
     * 解雇员工Servlet
     */
    @WebServlet("/admin/fire_employee")
    public class FireEmployeeServlet extends HttpServlet {
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 因为浏览器字符输出流有可能是中文,所以先设置中文编码
            response.setContentType("text/html; charset=utf-8");
    
            // 往浏览器里输出了,要获取浏览器字符输出流
            PrintWriter out = response.getWriter();
    
            out.write("<H1>解雇员工Servlet!!!</H1>");
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doPost(request, response);
        }
    }
  • HireEmployeeServlet

    java
    package com.futureweaver.web;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    // 雇佣员工
    @WebServlet("/admin/hire_employee")
    public class HireEmployeeServlet extends HttpServlet {
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 对中文做编集处理
            response.setContentType("text/html; charset=utf-8");
    
            // 要给用户一个结果,放到浏览器字符输出流当中
            PrintWriter out = response.getWriter();
    
            // 输出
            out.write("<H1>雇佣员工Servlet!!!</H1>");
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doPost(request, response);
        }
    }
  • HelloFilter.java

    java
    package com.futureweaver.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Enumeration;
    
    /**
     * 1. 新建普通的Java类,实现Filter接口,重写doFilter方法
     * 2. 配置Filter
     *  1. 配置类路径
     *  2. 配置映射路径
     */
    public class HelloFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
    
        // 因为Filter是Java EE当中的规范
        // 所以,当Filter要拦截请求时,Tomcat会自动为我们调用doFilter方法
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            // 拦截
            // 放行
            // 增强
    
            // 因为要使用request的getContextPath方法,此方法只在HttpServletRequest当中才会有
            // 所以将ServletRequest强转成HttpServletRequest
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
    
            System.out.println("Filter进入了!!!" + request.getServletPath());
    
            // 如果想要放行的话,那么必须要手动地编写代码
            // 手动地编写代码,要放行的话,需要使用filterChain对象
            filterChain.doFilter(servletRequest, servletResponse);
    
            // 所以没有编写代码的话,就是拦截
            System.out.println("Filter完成了!!!" + request.getServletPath());
        }
    
        @Override
        public void destroy() {
        }
    }
  • web.xml

    xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
      
        <!-- 1. 新建普通的Java类,实现Filter接口,重写doFilter方法 -->
        <!-- 2. 配置Filter -->
        <!--   1. 配置类路径 -->
        <!--   2. 配置映射路径 -->
      
        <!-- 配置映射路径 -->
        <filter-mapping>
            <!-- 为Filter起一个名字 -->
            <filter-name>HelloFilter</filter-name>
    
            <!-- 因为Filter要对资源进行拦截、增强、放行,所以需要配置这一个Filter要拦截哪些资源 -->
    
            <!-- /admin/hire... -->
            <url-pattern>/admin/hire_employee</url-pattern>
        </filter-mapping>
    
        <!-- 配置类路径 -->
        <filter>
            <filter-name>HelloFilter</filter-name>
    
            <!-- 从映射路径,到了Filter的类路径,那么当浏览器的地址和映射路径匹配到了之后,需要找到相应的Filter类 -->
            <!-- 因为类在不同的包当中,名字可以是同名的,所以要写绝对路径 -->
            <!-- 绝对路径就是包名+类名 -->
            <filter-class>com.futureweaver.filter.HelloFilter</filter-class>
        </filter>
    </web-app>

Filter的执行流程分析【重点】

FD033F61-7DFA-4748-9F41-F45D6E8522D9

注意:部署描述符非重点。

  1. Tomcat 启动时会将 web.xml 文件加载到 Tomcat 内存中
  2. Tomcat 在接收到 Web 请求时,会根据 web.xml 当中指定的资源路径去匹配相应的过滤器。
  3. 当浏览器地址栏中地址资源匹配到 web.xml 中的映射路径(url-pattern),会通过 web.xml 当中的类路径(filter-class)访问到对应的 Filter 类
  4. 在 Servlet 的 Java EE 规范当中,Tomcat 接收到 Web 请求时,它会去调用相应的 Filter 类当中的 doFilter 方法

过滤器的生命周期【重点】

编写测试类

java
package com.futureweaver.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

/**
 * 1. 新建普通的Java类,实现Filter接口,重写doFilter方法
 * 2. 配置Filter
 *  1. 配置类路径
 *  2. 配置映射路径
 */
public class HelloFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("HelloFilter被创建了!!!");
    }

    // 因为Filter是Java EE当中的规范
    // 所以,当Filter要拦截请求时,Tomcat会自动为我们调用doFilter方法
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 拦截
        // 放行
        // 增强

        // 因为要使用request的getContextPath方法,此方法只在HttpServletRequest当中才会有
        // 所以将ServletRequest强转成HttpServletRequest
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;

        System.out.println("Filter进入了!!!" + request.getServletPath());

        // 如果想要放行的话,那么必须要手动地编写代码
        // 手动地编写代码,要放行的话,需要使用filterChain对象
        filterChain.doFilter(servletRequest, servletResponse);

        // 所以没有编写代码的话,就是拦截
        System.out.println("Filter完成了!!!" + request.getServletPath());
    }

    @Override
    public void destroy() {
        System.out.println("HelloFilter被销毁了!!!");
    }
}

结论

  • 项目加载时被 Tomcat 创建
  • 项目卸载时被 Tomcat 销毁
  • Filter 只创建一次,Filter 线程不安全

过滤器与Web资源的关系

Association-Of-Filter-Resource

多个Web资源

  • web.xml

    xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!-- 1. 新建普通的Java类,实现Filter接口,重写doFilter方法 -->
        <!-- 2. 配置Filter -->
          <!-- 1. 配置类路径 -->
          <!-- 2. 配置映射路径 -->
      <filter-mapping>
            <!-- 为Filter起一个名字 -->
            <filter-name>HelloFilter</filter-name>
    
            <!-- 因为Filter要对资源进行拦截、增强、放行,所以需要配置这一个Filter要拦截哪些资源 -->
            <!-- <url-pattern>/fire_employee</url-pattern> -->
    
            <!-- 对多个Web资源进行过滤时,url-pattern可以写多条-->
            <!-- <url-pattern>/hire_employee</url-pattern> -->
    
            <!-- 两个Servlet -->
            <!-- 这两个Servlet都是有/admin打头 -->
            <!-- /admin/fire... -->
            <!-- /admin/hire... -->
            <url-pattern>/admin/*</url-pattern>
        </filter-mapping>
    
        <filter>
            <filter-name>HelloFilter</filter-name>
    
            <!-- 从映射路径,到了Filter的类路径,那么当浏览器的地址和映射路径匹配到了之后,需要找到相应的Filter类 -->
            <!-- 因为类在不同的包当中,名字可以是同名的,所以要写绝对路径 -->
            <!-- 绝对路径就是包名+类名 -->
            <filter-class>com.futureweaver.filter.HelloFilter</filter-class>
        </filter>
      
    </web-app>

多个过滤器

  • web.xml

    xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <!-- 配置Filter -->
        <!-- 配置类路径 -->
        <!-- 配置映射路径 -->
    
        <filter-mapping>
            <!-- 为Filter起一个名字 -->
            <filter-name>HelloFilter</filter-name>
    
            <!-- 因为Filter要对资源进行拦截、增强、放行,所以需要配置这一个Filter要拦截哪些资源 -->
            <!-- <url-pattern>/fire_employee</url-pattern> -->
    
            <!-- 对多个Web资源进行过滤时,url-pattern可以写多条 -->
            <!-- <url-pattern>/hire_employee</url-pattern> -->
    
            <!-- 两个Servlet -->
            <!-- 这两个Servlet都是有/admin打头 -->
            <!-- /admin/fire... -->
            <!-- /admin/hire... -->
            <url-pattern>/admin/*</url-pattern>
        </filter-mapping>
    
        <filter>
            <filter-name>HelloFilter</filter-name>
    
            <!-- 从映射路径,到了Filter的类路径,那么当浏览器的地址和映射路径匹配到了之后,需要找到相应的Filter类 -->
            <!-- 因为类在不同的包当中,名字可以是同名的,所以要写绝对路径 -->
            <!-- 绝对路径就是包名+类名 -->
            <filter-class>com.futureweaver.filter.HelloFilter</filter-class>
        </filter>
    
        <!-- 配置HelloFilter2 -->
        <!-- 配置Filter的映射路径 -->
        <filter-mapping>
            <filter-name>HelloFilter2</filter-name>
            <url-pattern>/admin/hire_employee</url-pattern>
        </filter-mapping>
    
        <!-- 配置Filter的类路径 -->
        <filter>
            <filter-name>HelloFilter2</filter-name>
            <filter-class>com.futureweaver.filter.HelloFilter2</filter-class>
        </filter>
    
    </web-app>
  • 控制台

    tex
    请求被 Filter1 拦截了: http://localhost:8080/day12_war_exploded/my_servlet
    请求被 Filter2 拦截了: http://localhost:8080/day12_war_exploded/my_servlet
    This is My Servlet Page
    响应被 Filter2 拦截了: http://localhost:8080/day12_war_exploded/my_servlet
    响应被 Filter1 拦截了: http://localhost:8080/day12_war_exploded/my_servlet

过滤器链

Filter-Chain

多个过滤器组成过滤器链,谁先执行,谁后执行?

在客户端访问服务器 web 资源时,服务器端为一个 web 资源,配置多个过滤器拦截 ,这多个过滤器,就会组成过滤器链 FilterChain, 调用 FilterChain 的 doFilter 表示要执行过滤器链下一个环节,如果当前过滤器已经是链上最后一个过滤器,就会执行目标资源

web 服务器根据 Filter 在 web.xml 文件中的注册顺序<filter-mapping>,决定先调用哪个 Filter

LoginServlet 用户访问是被允许

LoginSuccessServlet 用户直接访问是不被允许

LoginFailureServlet 用户直接访问是不被允许

过滤器深入

过滤器深入

过滤方式配置【重点】

指定 web.xml 当中的 dispatcher 标签

  • 【重点】REQUEST

    tex
    当HTTP请求到相应资源时会被过滤
  • 【重点】FORWARD

    tex
    当转发到相应资源时会被过滤
  • ERROR

    tex
    当发生错误,展示到错误页面时(web.xml配置<error-page>),会被过滤
  • INCLUDE

    tex
    当资源被其他页面引入(动态引入)时,会被过滤

过滤器初始化参数配置【了解】

  • web.xml

    xml
    <filter>
        <filter-name>Filter1</filter-name>
        <filter-class>com.futureweaver.filter.Filter1</filter-class>
    
        <!-- 配置初始化参数 -->
        <init-param>
            <param-name>name</param-name>
            <param-value>value</param-value>
        </init-param>
    </filter>
  • Filter1.java

    java
    package com.futureweaver.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class Filter1 implements Filter {
        private String nameParam;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
          
            // 取出在web.xml当中配置的参数
    
            // 读取所有的filter初始化参数
            Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
    
            // 遍历初始化参数名字
            while (initParameterNames.hasMoreElements()) {
                // 获取初始化参数名字元素
                String s = initParameterNames.nextElement();
    
                System.out.println("初始化参数" + s + ":" + filterConfig.getInitParameter(s));
            }
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
    
            System.out.println("请求被 Filter1 拦截了" + request.getRequestURL());
    
            filterChain.doFilter(request, response);
    
            System.out.println("响应被 Filter1 拦截了" + request.getRequestURL());
        }
    
        @Override
        public void destroy() {
    
        }
    }

小结

  • Filter 编写的步骤

    1. 新建一个普通的Java类,实现Filter接口,重写doFilter方法
    2. 配置Filter
      1. 类路径
      2. 映射路径
  • Filter 生命周期【重点】

    项目加载时,由Tomcat创建 项目卸载时,由Tomcat销毁

  • Servlet 生命周期

    第一次被访问,由Tomcat创建 项目卸载时,由Tomcat销毁

  • Request 生命周期

    服务器接收到请求时,由Tomcat创建 响应结束时,由Tomcat销毁

  • ServletContext 生命周期

    项目加载时,由Tomcat创建 项目卸载时,由Tomcat销毁

  • Session 生命周期

    用户第一次访问时,由Tomcat创建 session过期时,由Tomcat销毁

  • 过滤器与 Web 资源的关系

    同一个过滤器,对应多个web资源 url-pattern配置多个

    • 完全匹配
    • 目录匹配
    • 后缀名

    多个过滤器,对应同一个web资源 多个过滤器其中的url-pattern配置一样的

  • 过滤方式配置

    <dispatcher>REQUESTFORWARD

注解配置过滤器

java
package com.futureweaver.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 1. 新建普通的Java类,实现Filter接口,重写doFilter方法
 * 2. 配置Filter
 *  1. 配置Filter的类路径
 *  2. 配置Filter的映射路径
 */
@WebFilter("/resource_a")
public class AnnotationFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    // 当过滤器要进入时,Tomcat会自动地调用doFilter方法
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 强转
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        System.out.println("AnnotationFilter进入了" + request.getServletPath());

        filterChain.doFilter(request, response);

        System.out.println("AnnotationFilter离开了" + request.getServletPath());
    }

    @Override
    public void destroy() {

    }
}

注解Filter的执行顺序【了解】

  1. xml 配置方式的 Filter,执行顺序按照在 web.xml 中配置的顺序,从上到下执行;(filter-mapping)
  2. 注解开发的 Filter,执行的顺序是按照实现类的字母表顺序依次执行!

IDEA 模板

Filter-Template

案例

  • 利用过滤器技术,实现审计功能

  • 利用过滤器技术,实现全局乱码统一处理功能

分析

  • AuditFilter.java
  • EncodingFilter.java

学习目标总结

  • 能够独立完成过滤器入门案例
  • 能够说出过滤器的生命周期
  • 能够阐述 FilterChain 对象的使用
  • 能够阐述过滤器的拦截方式
  • 过滤器的作用【重点】
  • 过滤器的开发步骤【了解】
  • 过滤器的生命周期【重点】
  • 过滤器链的执行顺序【了解】
  • 注解配置 Filter 的执行顺序【了解】