热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JavaWeb笔记(8):Filter过滤器/ThreadLocal

目录1、Filter是什么过滤器2、Filter的工作流程图:3、Filter过滤器的使用步骤:4、Filter的生命周期5、FilterCo

目录

1、Filter 是什么过滤器

2、Filter 的工作流程图:

3、Filter 过滤器的使用步骤:

4、Filter 的生命周期

5、FilterConfig 类

6、FilterChain 过滤器链

7、Filter 的拦截路径

8、ThreadLocal 的使用

9、使用 Filter 和 ThreadLocal 组合管理事务




1、Filter 是什么过滤器


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

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


  • ①、权限检查
  • ②、日记操作
  • ③、事务管理
  • ……等等

2、Filter 的工作流程图:

Filter实现原理:

在这里插入图片描述


3、Filter 过滤器的使用步骤:


  • 1、编写一个类去实现 Filter 接口
  • 2、实现过滤方法 doFilter()
  • 3、到 web.xml 中去配置 Filter 的拦截路径

Filter 的代码:

public class AdminFilter implements Filter {/*** doFilter 方法,专门用于拦截请求。可以做权限检查*/@Overridepublic 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("/admin/a.html").forward(servletRequest,servletResponse);return;} else {// 让程序继续往下访问用户的目标资源filterChain.doFilter(servletRequest,servletResponse);}}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void destroy() {}
}

web.xml 中的配置:

AdminFiltercom.crane.filter.AdminFilterAdminFilter/admin/*

a.html 页面 == 登录表单

用户名:
密 码:

LoginServlet 程序

public class LoginServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 解决中文乱码问题req.setCharacterEncoding("UTF-8");resp.setContentType("text/html; charset=UTF-8");String username = req.getParameter("username");String password = req.getParameter("password");if ("Tommey周".equals(username) && "123456".equals(password)) {req.getSession().setAttribute("user",username);resp.getWriter().write("登录成功");} else {req.getRequestDispatcher("/admin/a.html").forward(req,resp);}}
}

web.xml中filter用于过滤/admin/*中的内容。 

过程:

1、访问http://localhost:8080/WebServlet_war_exploded/admin/a.html

2、提示先登录

 3、登陆成功后

4、访 问http://localhost:8080/WebServlet_war_exploded/admin/a.html。 正常


4、Filter 的生命周期

Filter 的生命周期包含几个方法


  • 1、构造器方法
  • 2、init 初始化方法
    • 第 1,2 步,在 web 工程启动的时候执行(Filter 已经创建)
  • 3、doFilter 过滤方法
    • 第 3 步,每次拦截到请求,就会执行
  • 4、destroy 销毁
    • 第 4 步,停止 web 工程的时候,就会执行(停止 web 工程,也会销毁 Filter 过滤器)

5、FilterConfig 类

FilterConfig 是 Filter 过滤器的配置文件类

Tomcat 每次创建 Filter 的时候,也会同时创建一个 FilterConfig 类,这里包含了 Filter 配置文件的配置信息。

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


  • 1、获取 Filter 的名称 filter-name 的内容
  • 2、获取在 Filter 中配置的 init-param 初始化参数
  • 3、获取 ServletContext 对象

public class AdminFilter implements Filter {@Overridepublic 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());}

web.xml代码:


AdminFiltercom.AdminFilterusernamerooturljdbc:mysql://localhost3306/test


6、FilterChain 过滤器链


  • Filter ========》过滤器
  • Chain =======》链,链条
  • FilterChain ===》 就是过滤器链(多个过滤器如何一起工作) 

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


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

FilterChain.doFilter()方法的作用


  • 1、执行下一个Filter过滤器(如果有Filter)
  • 2、执行目标资源(没有Filter)

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

案例:

public class Filter1 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("filter1 前置");System.out.println("filter1参数:" + servletRequest.getParameter("username"));System.out.println("filter1线程:" + Thread.currentThread().getName());filterChain.doFilter(servletRequest, servletResponse);System.out.println("filter1线程:" + Thread.currentThread().getName());System.out.println("filter1 后置");}@Overridepublic void destroy() {}
}public class Filter2 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("filter2 前置");System.out.println("filter2参数:" + servletRequest.getParameter("username"));System.out.println("filter2线程:" + Thread.currentThread().getName());filterChain.doFilter(servletRequest, servletResponse);System.out.println("filter2线程:" + Thread.currentThread().getName());System.out.println("filter2 后置");}@Overridepublic void destroy() {}
}


Filter1com.crane.filter.Filter1Filter1/target.jspFilter2com.crane.filter.Filter2 Filter2/target.jsp

<%&#64; page language&#61;"java" contentType&#61;"text/html" pageEncoding&#61;"GBK"%>



filter执行

<%System.out.println("target.jsp页面执行了");System.out.println("target.jsp页面 线程&#xff1a;" &#43; Thread.currentThread().getName());System.out.println("target.jsp页面参数&#xff1a;" &#43; request.getParameter("username"));
%>

http://localhost:8080/WebServlet_war_exploded/target.jsp?username&#61;li

filter1 前置
filter1线程&#xff1a;http-nio-8080-exec-7
filter2 前置
filter2线程&#xff1a;http-nio-8080-exec-7
target.jsp页面执行了
target.jsp页面 线程&#xff1a;http-nio-8080-exec-7
filter2线程&#xff1a;http-nio-8080-exec-7
filter2 后置
filter1线程&#xff1a;http-nio-8080-exec-7
filter1 后置



7、Filter 的拦截路径

1、精确匹配


  • /target.jsp
  • 配置的路径表示请求地址必须为&#xff1a;http://ip:port/工程路径/target.jsp

2、目录匹配


  • /admin/*
  • 配置的路径表示请求地址必须为&#xff1a;http://ip:port/工程路径/admin/*

3、后缀名匹配


  • *.html&#xff0c;配置的路径表示请求地址必须以.html 结尾才会拦截到
  • *.do&#xff0c;配置的路径表示请求地址必须以.do 结尾才会拦截到
  • *.action&#xff0c;配置的路径表示请求地址必须以.action 结尾才会拦截到

Filter 过滤器它只关心请求的地址是否匹配&#xff0c;不关心请求的资源是否存在


8、ThreadLocal 的使用


  • ThreadLocal 的作用&#xff0c;它可以解决多线程的数据安全问题。
  • ThreadLocal 它可以给当前线程关联一个数据&#xff08;可以是普通变量&#xff0c;可以是对象&#xff0c;也可以是数组&#xff0c;集合&#xff09;
  • ThreadLocal 的特点&#xff1a;
    • 1、ThreadLocal 可以为当前线程关联一个数据&#xff08;它可以像 Map 一样存取数据&#xff0c;key 为当前线程&#xff09;。
    • 2、每一个 ThreadLocal 对象&#xff0c;只能为当前线程关联一个数据&#xff0c;如果要为当前线程关联多个数据&#xff0c;就需要使用多个ThreadLocal 对象实例。
    • 3、每个 ThreadLocal 对象实例定义的时候&#xff0c;一般都是 static 类型
    • 4、ThreadLocal 中保存数据&#xff0c;在线程销毁后。会由 JVM 虚拟自动释放

示例代码&#xff1a;ThreadLocalTest 类

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

OrderService 类&#xff1a;

public class OrderService {public void createOrder() {String name &#61; Thread.currentThread().getName();System.out.println("OrderService 当前线程[" &#43; name &#43; "] 中保存的数据是&#xff1a;" &#43;ThreadLocalTest.threadLocal.get());new OrderDao().saveOrder();}
}

OrderDao 类&#xff1a;

public class OrderDao {public void saveOrder() {String name &#61; Thread.currentThread().getName();System.out.println("OrderDao 当前线程[" &#43; name &#43; "] 中保存的数据是&#xff1a;" &#43;ThreadLocalTest.threadLocal.get());}
}

 线程[Thread-1] 生成的随机数是&#xff1a;160
 线程[Thread-0] 生成的随机数是&#xff1a;445
 线程[Thread-2] 生成的随机数是&#xff1a;364
 在线程[Thread-0] 快结束时取出关联的数据是&#xff1a;445
 在线程[Thread-2] 快结束时取出关联的数据是&#xff1a;364
 在线程[Thread-1] 快结束时取出关联的数据是&#xff1a;160



9、使用 Filter 和 ThreadLocal 组合管理事务

图书系统中&#xff1a;选择购物车在结算时&#xff0c;在一个方法中实现了保存订单order、保存订单项order_item、更新库存和销量的操作&#xff0c;该操作必须保证事务。

如果没有事务&#xff0c;在保存订单后出错时&#xff0c;会出现以下落库问题&#xff1a;

此时订单表保存成功&#xff0c;订单项表 为空 &#xff0c;则表示用户付了该订单&#xff0c;而商家不知道该发货的商品是哪个。故需要事务。

1、JDBC事务

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


  • 原理分析图

注意&#xff1a;所有操作在try...catch时需要抛异常到回滚处&#xff0c;不能只try.catch不抛。


  • JdbcUtils工具类的修改&#xff1a;

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

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

  • 原理分析图&#xff1a;

在这里插入图片描述 Filter 类代码&#xff1a; 

public class TransactionFilter implements Filter {&#64;Overridepublic void init(FilterConfig filterConfig) throws ServletException {}&#64;Overridepublic 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();}}&#64;Overridepublic void destroy() {}
}

web.xml

TransactionFiltercom.TransactionFilter

TransactionFilter/*


  • 把 BaseServlet 中的异常往外抛给 Filter 过滤器

public abstract class BaseServlet extends HttpServlet {&#64;Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 解决 post 请求中文乱码问题// 一定要在获取请求参数之前调用才有效req.setCharacterEncoding("UTF-8");String action &#61; req.getParameter("action");try {// 获取 action 业务鉴别字符串&#xff0c;获取相应的业务 方法反射对象Method method &#61; this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);System.out.println(method);// 调用目标业务 方法method.invoke(this, req, resp);} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);// 把异常抛给 Filter 过滤器}}
}

3将所有异常都统一交给 Tomcat &#xff0c;让 Tomcat 展示友好的错误信息页面

  • 在 web.xml 中我们可以通过错误页面配置来进行管理


500/pages/error/error500.jsp


404/pages/error/error404.jsp


推荐阅读
  • 长期从事ABAP开发工作的专业人士,在面对行业新趋势时,往往需要重新审视自己的发展方向。本文探讨了几位资深专家对ABAP未来走向的看法,以及开发者应如何调整技能以适应新的技术环境。 ... [详细]
  • 本文详细介绍了如何正确设置Shadowsocks公共代理,包括调整超时设置、检查系统限制、防止滥用及遵守DMCA法规等关键步骤。 ... [详细]
  • 根据官方定义,RxJava是一种用于异步编程和可观察数据流的API。其核心特性在于流式处理能力和丰富的操作符支持。 ... [详细]
  • 本文将探讨如何在 Struts2 中使用 ActionContext 和 ServletActionContext 来获取请求参数和会话信息,同时解释它们的内部机制和最佳实践。 ... [详细]
  • Nginx 启动命令及 Systemctl 配置详解
    本文详细介绍了在未配置和已配置 Systemctl 的情况下启动 Nginx 的方法,并提供了详细的配置步骤和命令示例。 ... [详细]
  • 本文探讨了如何在PHP与MySQL环境中实现高效的分页查询,包括基本的分页实现、性能优化技巧以及高级的分页策略。 ... [详细]
  • 本文详细探讨了在Java中如何将图像对象转换为文件和字节数组(Byte[])的技术。虽然网络上存在大量相关资料,但实际操作时仍需注意细节。本文通过使用JMSL 4.0库中的图表对象作为示例,提供了一种实用的方法。 ... [详细]
  • 在尝试加载支持推送通知的iOS应用程序的Ad Hoc构建时,遇到了‘no valid aps-environment entitlement found for application’的错误提示。本文将探讨此错误的原因及多种可能的解决方案。 ... [详细]
  • 本文详细介绍了在 CentOS 系统中如何创建和管理 SWAP 分区,包括临时创建交换文件、永久性增加交换空间的方法,以及如何手动释放内存缓存。 ... [详细]
  • 问题描述现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能;在实际开发过程中 ... [详细]
  • 洛谷 P4009 汽车加油行驶问题 解析
    探讨了经典算法题目——汽车加油行驶问题,通过网络流和费用流的视角,深入解析了该问题的解决方案。本文将详细阐述如何利用最短路径算法解决这一问题,并提供详细的代码实现。 ... [详细]
  • Requests库的基本使用方法
    本文介绍了Python中Requests库的基础用法,包括如何安装、GET和POST请求的实现、如何处理Cookies和Headers,以及如何解析JSON响应。相比urllib库,Requests库提供了更为简洁高效的接口来处理HTTP请求。 ... [详细]
  • 在开发iOS应用时,面对不同状态(如数据加载成功、无数据、未登录、网络异常等)的界面管理,如何实现既高效又美观的用户体验?本文探讨了几种最佳实践方法。 ... [详细]
  • 本文探讨了在使用JavaMail发送电子邮件时,抄送功能未能正常工作的问题,并提供了详细的代码示例和解决方法。 ... [详细]
  • 如题:2017年10月分析:还记得在没有智能手机的年代大概就是12年前吧,手机上都会有WAP浏览器。当时没接触网络原理,也不 ... [详细]
author-avatar
小丽丽很可爱
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有