zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Github注册过程以及对管理软件的了解
    进度总结
    总体心得
    学车后的领悟
    打工心得
    关于传统文化的对话实践计划书
    软件工程-课程总结
    结对编程项目---四则运算
    作业三
    目前流行的源程序版本管理软件和项目管理软件都有哪些?各有什么优缺点
  • 原文地址:https://www.cnblogs.com/geekfx/p/12828231.html
Copyright © 2011-2022 走看看