Filter

Filter

Filter 过滤器它是 JavaWeb 的三大组件之一
三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器
Filter 过滤器它是 JavaEE 的规范。也就是接口
Filter 过滤器它的作用是:拦截请求,过滤响应

拦截请求常见的应用场景有:

  1. 权限检查
  2. 日记操作
  3. 事务管理
    等等………

geekfx.png

Filter 初体验

首先创建一个 Java 类,实现 Filter 接口,可以看到实现的方法:

public class AdminFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

    }

    @Override
    public void destroy() {

    }
}

使用之前需要去 web.xml 文件配置访问路径:

<!-- filter 标签用于配置 Filter 过滤器 -->
<filter>
    <!-- Filter 过滤器的别名 -->
    <filter-name>AdminFilter</filter-name>
    <!-- Filter 过滤器的全类名 -->
    <filter-class>work.jkfx.filter.AdminFilter</filter-class>
</filter>
<!-- filter-mapping 用于配置 Filter 过滤器的拦截路径 -->
<filter-mapping>
    <!-- 指定 Filter 过滤器的别名 -->
    <filter-name>AdminFilter</filter-name>
    <!-- 指定 Filter 过滤器的拦截路径
        / 斜杠表示:http://ip:port/工程路径/
        /admin/* 表示:http://ip:port/工程路径/admin/所有文件
        此资源路径会先经过 Filter 过滤器
    -->
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

每次访问 /admin 目录下的任何文件,都会先经过 AdminFilter 过滤器检查:

/*
    doFilter 方法专门用于拦截请求,可做权限检查
 */
HttpServletRequest req = (HttpServletRequest) request;
Object user = req.getSession().getAttribute("user");
if(user == null) {
    // 假设用户还没有登录 请求转发到登录界面
    req.getRequestDispatcher("/login.jsp").forward(request, response);
} else {
    // 假设用户已经登录 放行
    chain.doFilter(request, response);
}

Filter 生命周期

  1. 构造器方法
  2. init 初始化方法
  3. doFilter 过滤方法
  4. destroy 销毁方法

第 1、2 步在 web 工程启动时执行
第 3 步在拦截到请求时执行
第 4 步在 web 工程停止时执行

public class FirstFilter implements Filter {
    public FirstFilter() {
        System.out.println("FirstFilter()");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init()");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("doFilter()");
    }

    @Override
    public void destroy() {
        System.out.println("destroy()");
    }
}

geekfx.png

FilterConfig 类

FilterConfig 接口的定义:

public interface FilterConfig {

    public String getFilterName();

    public ServletContext getServletContext();

    public String getInitParameter(String name);

    public Enumeration<String> getInitParameterNames();

}

FilterConfig 类见名知义,它是 Filter 过滤器的配置文件类
Tomcat 每次创建 Filter 的时候,也会同时创建一个 FilterConfig 类,这里包含了 Filter 配置文件的配置信息

FilterConfig 类的作用是获取 filter 过滤器的配置内容:

  1. 获取 Filter 的名称 filter-name 的内容
  2. 获取在 Filter 中配置的 init-param 初始化参数
  3. 获取 ServletContext 对象
<filter>
    <filter-name>FirstFilter</filter-name>
    <filter-class>work.jkfx.filter.FirstFilter</filter-class>
    <init-param>
        <param-name>key1</param-name>
        <param-value>value1</param-value>
    </init-param>
    <init-param>
        <param-name>key2</param-name>
        <param-value>value2</param-value>
    </init-param>
</filter>
public void init(FilterConfig filterConfig) throws ServletException {
    String filterName = filterConfig.getFilterName();
    System.out.println("filter-name = " + filterName);
    String key1 = filterConfig.getInitParameter("key1");
    System.out.println("key1 = " + key1);
    String key2 = filterConfig.getInitParameter("key2");
    System.out.println("key2 = " + key2);
    ServletContext servletContext = filterConfig.getServletContext();
    System.out.println("servletContext = " + servletContext);
}

geekfx.png

FilterChain 过滤器链

FilterChain 就是过滤器链(多个过滤器如何一起工作)

geekfx.png

现有一个 jsp 页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>目标jsp文件</title>
</head>
<body>
    <%
        System.out.println("target.jsp 线程:" + Thread.currentThread().getName());
        System.out.println("target.jsp 文件的代码");
    %>
</body>
</html>

现有两个 Filter 类:Filter1、Filter2

/**
 * Filter1 过滤器
 * @author geekfx
 * @create 2020-05-03 12:47
 */
public class Filter1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Filter1 的前置代码");
        chain.doFilter(request, response);
        System.out.println("Filter1 的后置代码");
    }

    @Override
    public void destroy() {

    }
}
/**
 * Filter2 过滤器
 * @author geekfx
 * @create 2020-05-03 12:47
 */
public class Filter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Filter2 的前置代码");
        chain.doFilter(request, response);
        System.out.println("Filter2 的后置代码");
    }

    @Override
    public void destroy() {

    }
}

这两个 Filter 过滤器在 web.xml 配置顺序:

<filter>
    <filter-name>Filter1</filter-name>
    <filter-class>work.jkfx.filter.Filter1</filter-class>
</filter>
<filter-mapping>
    <filter-name>Filter1</filter-name>
    <url-pattern>/target.jsp</url-pattern>
</filter-mapping>

<filter>
    <filter-name>Filter2</filter-name>
    <filter-class>work.jkfx.filter.Filter2</filter-class>
</filter>
<filter-mapping>
    <filter-name>Filter2</filter-name>
    <url-pattern>/target.jsp</url-pattern>
</filter-mapping>

启动服务器,在浏览器访问 /target.jsp 文件,看到控制台输出:

geekfx.png

可以看到各个代码的执行顺序,如果将 Filter2 过滤器的 doFilter 方法注释掉,再次访问 target.jsp 文件,看到控制台输出:

geekfx.png

将在 Filter2 的方法中断开不访问目标资源,直接原路返回
若将 Filter1 的 doFilter 方法注释掉,将直接原路返回,Filter2 过滤器也不会被执行:

geekfx.png

Filter1 和 Filter2 的执行顺序是它们在 web.xml 配置文件中从上到下的配置顺序

Filter 过滤器的特点:

  1. 所有的 Filter 和目标资源默认都在同一个线程中
  2. 多个 Filter 过滤器共同执行时候,他们使用同一个 Request 对象

在两个 Filter 过滤器的 doFilter 方法中加入两行代码:

// Filter1 过滤器的 doFilter 方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("Filter1 线程:" + Thread.currentThread().getName());
    System.out.println("Filter1 Request 对象:" + request);
    System.out.println("Filter1 的前置代码");
    chain.doFilter(request, response);
    System.out.println("Filter1 的后置代码");
}
// Filter2 过滤器的 doFilter 方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("Filter2 线程:" + Thread.currentThread().getName());
    System.out.println("Filter2 Request 对象:" + request);
    System.out.println("Filter2 的前置代码");
    chain.doFilter(request, response);
    System.out.println("Filter2 的后置代码");
}
<%
    System.out.println("target.jsp 线程:" + Thread.currentThread().getName());
    System.out.println("target.jsp Request 对象:" + request);
    System.out.println("target.jsp 文件的代码");
%>

启动服务器访问目标资源:

geekfx.png

Filter 的拦截路径

  • 精确匹配
    <url-pattern>/target.jsp</url-pattern>
    以上配置的路径,表示请求地址必须为: http://ip:port/工程路径/target.jsp 才会拦截
  • 目录匹配
    <url-pattern>/admin/*</url-pattern>
    以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/admin/ 文件夹下的资源才会拦截
  • 后缀名匹配
    <url-pattern>*.jpg</url-pattern>
    以上配置的路径,表示请求地址必须为 .jpg 结尾的资源才会拦截

Filter 过滤器只在乎请求地址是否匹配,不在乎请求资源是否存在

ThreadLocal

ThreadLocal 的作用,它可以解决多线程的数据安全问题
ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)

ThreadLocal 的特点:

  1. ThreadLocal 可以为当前线程关联一个数据(它可以像 Map 一样存取数据,key 为当前线程)
  2. 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个 ThreadLocal 对象实例
  3. 每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型
  4. ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放
/**
 * 模拟 ThreadLocal 的使用 使用线程安全的 Map 集合
 * @author geekfx
 * @create 2020-05-03 19:54
 */
public class ThreadLocalTest {

    private static Map<String, Integer> data = new Hashtable<>();
    private static Random random = new Random();

    private static class Task implements Runnable {
        @Override
        public void run() {
            // 在 run 方法中 随机生成一个随机数(线程要关联的数据) 以当前线程的 name 为 key 保存到 map 中
            int i = random.nextInt(100);
            // 获取当前线程名
            String name = Thread.currentThread().getName();
            System.out.println("线程[" + name + "]生成的随机数:" + i);
            data.put(name, i);

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 在 run 方法结束之前 以当前线程名为 key 取出 value 并打印
            Object o = data.get(name);
            System.out.println("线程[" + name + "]结束前获取的 value = " + o);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Task()).start();
        }
    }
}

再来看一下 ThreadLocal 类的使用:

现有两个类:

/**
 * @author geekfx
 * @create 2020-05-03 21:14
 */
public class OrderTest {
    public void test() {
        String name = Thread.currentThread().getName();
        Integer integer = ThreadLocalTest.threadLocal.get();
        System.out.println("OrderTest 类下的线程[" + name + "]获取到的数据:" + integer);
    }
}
/**
 * @author geekfx
 * @create 2020-05-03 21:17
 */
public class DaoTest {
    public void test() {
        String name = Thread.currentThread().getName();
        Integer integer = ThreadLocalTest.threadLocal.get();
        System.out.println("DaoTest 类的线程[" + name + "]获取到的数据:" + integer);
    }
}

在 ThreadLocal 类下将刚刚的 Map 改成 ThreadLocal 对象:

/**
 * @author geekfx
 * @create 2020-05-03 19:54
 */
public class ThreadLocalTest {

    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    private static Random random = new Random();

    private static class Task implements Runnable {
        @Override
        public void run() {
            // 在 run 方法中 随机生成一个随机数(线程要关联的数据)
            int i = random.nextInt(100);
            // 获取当前线程名
            String name = Thread.currentThread().getName();
            System.out.println("线程[" + name + "]生成的随机数:" + i);
            threadLocal.set(i);

            new OrderTest().test();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new DaoTest().test();

            Integer o = threadLocal.get();
            System.out.println("线程[" + name + "]结束前获取的 value = " + o);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Task()).start();
        }
    }
}

执行 main 方法,查看运行结果:

线程[Thread-0]生成的随机数:96
线程[Thread-2]生成的随机数:27
线程[Thread-1]生成的随机数:54
OrderTest 类下的线程[Thread-0]获取到的数据:96
OrderTest 类下的线程[Thread-1]获取到的数据:54
OrderTest 类下的线程[Thread-2]获取到的数据:27
DaoTest 类的线程[Thread-0]获取到的数据:96
线程[Thread-0]结束前获取的 value = 96
DaoTest 类的线程[Thread-2]获取到的数据:27
线程[Thread-2]结束前获取的 value = 27
DaoTest 类的线程[Thread-1]获取到的数据:54
线程[Thread-1]结束前获取的 value = 54

可以发现使用 ThreadLocal 可以将一个线程和一个数据关联起来,不管是哪个类或哪个方法,只要是同一个线程,就可以获取到关联的数据

Filter 结合 ThreadLocal 实现事务管理

geekfx.png

使用 ThreadLocal 重构 JdbcUtils 的 getConnection 和 close 的代码
如果想实现事务的管理,需要为每一个 Service 的每一个方法都加上 try-catch 捕获异常然后回滚事务
如果有 1 万个业务功能就会有 1 万条重复的代码
不要造重复的轮子
可以使用 Filter 实现将所有请求捕获 然后执行 doFilter 方法间接的执行了 Service 的方法
在 Filter 的 doFilter 中捕获异常处理提交事务或者回滚事务
相当于给所有的 Service 加上了事务的管理

geekfx.png

/**
 * 通过 Filter 结合 ThreadLocal 实现对事务的管理
 * @author geekfx
 * @create 2020-05-03 22:17
 */
public class TransactionFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            // 所有的请求都会被这个 Filter 拦截
            // 直接放行 相当于间接的调用了 Service 层
            chain.doFilter(request, response);
            // 正常运行后提交下事务
            JdbcUtils.commitAndClose();
        } catch (Exception e) {
            // 一旦出现了错误 就回滚事务
            JdbcUtils.rollbackAndClose();
            e.printStackTrace();
            // 将错误抛给 Tomcat 服务器 由 Tomcat 统一处理
            throw RuntimeException(e);
        }
    }

    @Override
    public void destroy() {

    }
}

配置 TransactionFilter 的拦截地址:

<filter>
    <filter-name>TransactionFilter</filter-name>
    <filter-class>work.jkfx.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>TransactionFilter</filter-name>
    <!-- 表示所有的请求都被这个 Filter 拦截 统一处理 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

Tomcat 处理错误

将所有的错误交给 Tomcat 服务器并配置 web.xml 文件
说明出现了哪种错误跳转到哪个页面

<!-- error-page 标签用于配置出现错误时的处理 -->
<error-page>
    <!-- error-code 表示出错的响应码 -->
    <error-code>404</error-code>
    <!-- location 标签表示出现了对应的响应码错误时跳转的页面 -->
    <location>/pages/error/error404.jsp</location>
</error-page>

<error-page>
    <error-code>500</error-code>
    <location>/pages/error/error500.jsp</location>
</error-page>
不一定每天 code well 但要每天 live well
原文地址:https://www.cnblogs.com/geekfx/p/12828231.html