Filter过滤器使用以及和ThreadLocal的搭配使用

Filter过滤器

什么是Filter过滤器?

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

拦截请求常见的应用场景有:
1、权限检查
2、日记操作
3、事务管理
……等等

初始Filter过滤器

要求:在你的 web 工程下,有一个 admin 目录。这个 admin 目录下的所有资源(html 页面、jpg 图片、jsp 文件、等等)都必须是用户登录之后才允许访问。

思考:根据之前我们学过内容。我们知道,用户登录之后都会把用户登录的信息保存到 Session 域中。所以要检查用户是否登录,可以判断 Session 中否包含有用户登录的信息即可!!!

原来我们的做法:在jsp页面进行判断,如果不存在该对象就转发请求

<%
        Object user = session.getAttribute("user");
// 如果等于 null,说明还没有登录
        if (user == null) {
            request.getRequestDispatcher("/login.jsp").forward(request,response);
            return;
        }
%>

同样,我们也可以通过过滤器来实现:
在这里插入图片描述
Filter 过滤器的使用步骤:
1、编写一个类去实现 Filter 接口
2、实现过滤方法 doFilter()
3、到 web.xml 中去配置 Filter 的拦截路径(url或者是配置servletname)

java代码:

package Test;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @Author: yyyzl
 * @Date: 2020/10/10
 **/
public class AdminFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
    //doFilter:专门用于拦截请求。可以做权限检查
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpSession session = httpServletRequest.getSession();
        Object user = session.getAttribute("user");
        // 如果等于 null,说明还没有登录
        if (user == null) {
            servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
            return;
        } else {
            // 让程序继续往下访问用户的目标资源
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }

    @Override
    public void destroy() {

    }
}

web.xml文件中的配置:

  <!--filter 标签用于配置一个 Filter 过滤器-->
  <filter>
    <!--给 filter 起一个别名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置 filter 的全类名-->
    <filter-class>Test.AdminFilter</filter-class>
  </filter>
  <!--filter-mapping 配置 Filter 过滤器的拦截路径-->
  <filter-mapping>
    <!--filter-name 表示当前的拦截路径给哪个 filter 使用-->
    <filter-name>AdminFilter</filter-name>
    <!--url-pattern 配置拦截路径
    / 表示请求地址为:http://ip:port/工程路径/ 映射到 IDEA 的 web 目录
    /admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
    -->
    <url-pattern>/*</url-pattern>
  </filter-mapping>

上面是使用url-pattern过滤,还可以指定特定的servlet过滤

<servlet-name>helloSevlet</servlet-name>

可以简单设置下jsp页面就可以输网址继续跳转啦:

登录界面
<% HttpSession session1 = request.getSession();
session1.setAttribute("user",1);%>
指定的servlet 和过滤指定的请求

指定特定的servlet过滤

  <filter-mapping>
    <!--filter-name 表示当前的拦截路径给哪个 filter 使用-->
    <filter-name>AdminFilter</filter-name>
    <!--url-pattern 配置拦截路径
    / 表示请求地址为:http://ip:port/工程路径/ 映射到 IDEA 的 web 目录
    /admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
    -->
    <servlet-name>helloSevlet</servlet-name>
  </filter-mapping>

< dispatcher>是过滤指定的请求

  <filter-mapping>
    <!--filter-name 表示当前的拦截路径给哪个 filter 使用-->
    <filter-name>AdminFilter</filter-name>
    <!--url-pattern 配置拦截路径
    / 表示请求地址为:http://ip:port/工程路径/ 映射到 IDEA 的 web 目录
    /admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
    -->
    <servlet-name>helloSevlet</servlet-name>
    <dispatcher>XXX</dispatcher>
  </filter-mapping>

注意< dispatcher></ dispatcher>必须写在filter-mapping的最后。dispatcher的前提条件当然是要先满足url-pattern,然后dispatcher有四种可能的属性:

1、REQUEST
只要发起的操作是一次HTTP请求,比如请求某个URL发起了一个GET请求、表单提交方式为POST时提交表单则发起了一个POST请求、表单提交方式为GET时提交表单则发起了一次GET请求、一次重定向则前后相当于发起了两次请求,这些情况下有几次请求就会走几次指定过滤器

2、FOWARD
只有当当前页面是通过请求转发转发过来的场景,才会走指定的过滤器

3、INCLUDE
只要是通过<jsp:include page=“xxx.jsp” />,嵌入进来的页面,每嵌入的一个页面,都会走一次指定的过滤器

4、ERROR
这个可能开发者不是很熟悉,意思是当触发了一次error的时候,就会走一次指定的过滤器。什么叫做触发error,举个例子,我在web.xml里面配置了< error-page></ error-page>:

FilterConfig类

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

FilterConfig 类的作用是获取 filter 过滤器的配置内容
1、获取 Filter 的名称 filter-name 的内容
2、获取在 Filter 中配置的 init-param 初始化参数
3、获取 ServletContext 对象

web.xml文件:

 <!--filter 标签用于配置一个 Filter 过滤器-->
  <filter>
    <!--给 filter 起一个别名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置 filter 的全类名-->
    <filter-class>Test.AdminFilter</filter-class>
    <init-param>
      <param-name>username</param-name>
      <param-value>root</param-value>
    </init-param>
    <init-param>
      <param-name>url</param-name>
      <param-value>jdbc:mysql://localhost3306/test</param-value>
    </init-param>
  </filter>

java文件中获取:

获取方法有:

  • filterConfig.getFilterName():获取 Filter 的名称 filter-name 的内容
  • filterConfig.getInitParameter(String str):获取在 web.xml 中配置的 init-param 初始化参数
  • filterConfig.getServletContext():获取 ServletContext 对象
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter 的 init(FilterConfig filterConfig)初始化");
        // 1、获取 Filter 的名称 filter-name 的内容
        System.out.println("filter-name 的值是:" + filterConfig.getFilterName());
        // 2、获取在 web.xml 中配置的 init-param 初始化参数
        System.out.println("初始化参数 username 的值是:" + filterConfig.getInitParameter("username"));
        System.out.println("初始化参数 url 的值是:" + filterConfig.getInitParameter("url"));
        // 3、获取 ServletContext 对象
        System.out.println(filterConfig.getServletContext());
    }

FilterChain 过滤器链

Filter 过滤器
Chain 链,链条
FilterChain 就是过滤器链(多个过滤器如何一起工作)
在这里插入图片描述
filterChain.doFilter(servletRequest,servletResponse)的作用:

  1. 执行下一个filter过滤器(如果有其他的filter过滤器)
  2. 执行目标资源(如果没有filter过滤器)

注:在多个filter过滤器执行的时候,他们执行的优先顺序是由他们在web.xml中从上到下配置的顺序决定!!!

多个Filter过滤器执行的特点:

  1. 所有filter和目标资源都默认执行在同一线程中
  2. 多个filter共同执行时,他们都使用同一个request请求(上面图中可以看出是请求的传递)

Filter的拦截路径

精确匹配
<url-pattern>/target.jsp</url-pattern>

以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/target.jsp

目录匹配
<url-pattern>/admin/*</url-pattern>

以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/admin/*

后缀名匹配
<url-pattern>*.html</url-pattern>

以上配置的路径,表示请求地址必须以.html 结尾才会拦截到

<url-pattern>*.do</url-pattern>

以上配置的路径,表示请求地址必须以.do 结尾才会拦截到

<url-pattern>*.action</url-pattern>

以上配置的路径,表示请求地址必须以.action结尾才会拦截到

注:Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在!!!

ThreadLocal的使用

ThreadLocal 的作用,它可以解决多线程的数据安全问题。

ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)

ThreadLocal 的特点:
1、ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程)
2、每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个
ThreadLocal 对象实例。
3、每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型
4、ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放。

实例:
通过内部调用,我们可以发现调用的线程都是一样的

    public class ThreadLocalTest {
        // public static Map<String,Object> data = new Hashtable<String,Object>();
        public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
        private static Random random = new Random();
        public static class Task implements Runnable {
            @Override
            public void run() {
				// 在 Run 方法中,随机生成一个变量(线程要关联的数据),然后以当前线程名为 key 保存到 map 中
                Integer i = random.nextInt(1000);
				// 获取当前线程名
                String name = Thread.currentThread().getName();
                System.out.println("线程["+name+"]生成的随机数是:" + i);
				// data.put(name,i);
                threadLocal.set(i);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                new OrderService().createOrder();
				// 在 Run 方法结束之前,以当前线程名获取出数据并打印。查看是否可以取出操作
				// Object o = data.get(name);
                Object o = threadLocal.get();
                System.out.println("在线程["+name+"]快结束时取出关联的数据是:" + o);
            }
        }
        public static void main(String[] args) {
            for (int i = 0; i < 3; i++){
                new Thread(new Task()).start();
            }
        }
    }
   public class OrderService {
        public void createOrder(){
            String name = Thread.currentThread().getName();
            System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" +
                    ThreadLocalTest.threadLocal.get());
            new OrderDao().saveOrder();
        }
    }
    public class OrderDao {
        public void saveOrder(){
            String name = Thread.currentThread().getName();
            System.out.println("OrderDao 当前线程[" + name + "]中保存的数据是:" +
                    ThreadLocalTest.threadLocal.get());
        }
    }

使用Filter和ThreadLocal组合管理事务

使用 ThreadLocal 来确保所有 dao 操作都在同一个 Connection 连接对象中完成

在这里插入图片描述
要确保所有操作要么都成功,要么都失败,就必须要使用数据库的事务。
要确保所有操作都在一个事务内,就必须要确保所有操作都使用同一个Connection连接对象。
如何确保所有操作都使用同一个Connection连接对象?
可以使用ThreadLocal对象。来确保所有操作使用同一个Connection连接对象。
ThreadLocal要确保所有操作都使用同一个Connection连接对象。那么操作的前提是所有操作都必须在同一个线程中完成~

示例代码:

public class JdbcUtils {
    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();
    static {
        try {
            Properties properties = new Properties();
// 读取 jdbc.properties 属性配置文件
            InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 从流中加载数据
            properties.load(inputStream);
// 创建 数据库连接 池
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取数据库连接池中的连接
     * @return 如果返回 null,说明获取连接失败<br/>有值就是获取连接成功
     */
    public static Connection getConnection(){
        Connection conn = conns.get();
        if (conn == null) {
            try {
                conn = dataSource.getConnection();//从数据库连接池中获取连接
                conns.set(conn); // 保存到 ThreadLocal 对象中,供后面的 jdbc 操作使用
                conn.setAutoCommit(false); // 设置为手动管理事务
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;
    }
    /**
     * 提交事务,并关闭释放连接
     */
    public static void commitAndClose(){
        Connection connection = conns.get();
        if (connection != null) { // 如果不等于 null,说明 之前使用过连接,操作过数据库
            try {
                connection.commit(); // 提交 事务
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    connection.close(); // 关闭连接,资源资源
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
// 一定要执行 remove 操作,否则就会出错。(因为 Tomcat 服务器底层使用了线程池技术)
        conns.remove();
    }
    /**
     * 回滚事务,并关闭释放连接
     */
    public static void rollbackAndClose(){
        Connection connection = conns.get();
        if (connection != null) { // 如果不等于 null,说明 之前使用过连接,操作过数据库
            try {
                connection.rollback();//回滚事务
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    connection.close(); // 关闭连接,资源资源
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
// 一定要执行 remove 操作,否则就会出错。(因为 Tomcat 服务器底层使用了线程池技术)
        conns.remove();
    }


使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch。来进行实现的管理

在这里插入图片描述
按照上面的分析示意,那么就可以使用一个Filter。一次性,统一的给所有service的方法统一加上trycatch来实现事务的管理。(就是所有异常抛给过滤器,统一管理事务)

java filter代码:

public class TransactionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain
            filterChain) throws IOException, ServletException {
        try {
            filterChain.doFilter(servletRequest, servletResponse);
            JdbcUtils.commitAndClose();// 提交事务
        } catch (Exception e) {
            JdbcUtils.rollbackAndClose();//回滚事务
            e.printStackTrace();
        }
    }
}

web.xml中的配置:

  <filter>
    <filter-name>TransactionFilter</filter-name>
    <filter-class>Test.TransactionFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>TransactionFilter</filter-name>
    <!-- /* 表示当前工程下所有请求 -->
    <url-pattern>/*</url-pattern>
  </filter-mapping>

具体功能(doget、dopost、service)实现代码记得要把异常往外面抛:

throw new RuntimeException(e);// 把异常抛给 Filter 过滤器

将所有异常都统一交给 Tomcat,让 Tomcat 展示友好的错误信息页面(error-page)

error-page:标签配置,服务器出错之后,自动跳转的页面
error-code:是错误类型
location:标签表示。要跳转去的页面路径

  <!--error-page 标签配置,服务器出错之后,自动跳转的页面-->
  <error-page>
    <!--error-code 是错误类型-->
    <error-code>500</error-code>
    <!--location 标签表示。要跳转去的页面路径-->
    <location>/pages/error/error500.jsp</location>
  </error-page>
  <!--error-page 标签配置,服务器出错之后,自动跳转的页面-->
  <error-page>
    <!--error-code 是错误类型-->
    <error-code>404</error-code>
    <!--location 标签表示。要跳转去的页面路径-->
    <location>/pages/error/error404.jsp</location>
  </error-page>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值