javaweb——Filter——20200703

    技术2024-06-17  84

    文章目录

    1. FILTER过滤器是什么2. 简单测试FilterConfig类多个过滤器的执行拦截路径精确匹配目录匹配后缀名匹配 ThreadLocal类特点ThreadLocal测试 Filter和ThreadLocal实现数据库的事务管理使用Filter给所有的service方法加上try-catchTomcat对异常统一管理

    1. FILTER过滤器是什么

    它的作用是 拦截请求,过滤响应拦截请求常见的应用场景: 权限检查日志操作事务管理……

    2. 简单测试

    要求:web工程下有一个admin的目录。这个admin目录下的所有资源必须要在用户登录之后才允许访问。

    原理: Filter过滤器检查用户权限,有权限就放行,无权限挑战登录页面,不允许其访问。

    实现javax.servlet.Filter接口过滤方法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"); if (user==null){ servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse); }else { //让程序继续往下访问用户的目标资源 filterChain.doFilter(servletRequest,servletResponse); } } 配置web.xml <!-- 配置过滤器--> <filter> <filter-name>AdminFilter</filter-name> <filter-class>com.bookstore.filter.AdminFilter</filter-class> </filter> <filter-mapping> <filter-name>AdminFilter</filter-name> <!--配置拦截路径,/ 表示请求地址为:http://ip:port/工程路径/ 映射到IDEA的web目录 '/admin/*' 表示http://ip:port/工程路径/admin/*,--> <url-pattern>/admin/*</url-pattern> </filter-mapping>

    FilterConfig类

    FilterConfig类是Filter的配置文件类。tomcat每次创建filter的时候,也会同时创建一个FilterConfig类,这里包含了Filter配置文件的配置信息。FilterConfig类的作用是获取Filter过滤器的配置内容 名称 <filter-name>初始化参数<init-param>(可配置多组)ServletContext对象 <filter> <filter-name>AdminFilter</filter-name> <filter-class>com.bookstore.filter.AdminFilter</filter-class> <init-param> <param-name>name1</param-name> <param-value>value1</param-value> </init-param> </filter>

    多个过滤器的执行

    filterChain.doFilter()的作用 执行下一个Filter过滤器(如果存在)执行目标资源 执行顺序,安装在xml文件中的顺序,依次拦截,顺利执行后再依次返回,或者跳出“链条”所有filter和目标资源默认在同一个线程中执行。共同执行,它们使用同一个request对象。

    拦截路径

    精确匹配

    <url-pattern>/admin/a.jsp</url-pattern>

    目录匹配

    <url-pattern>/admin/*</url-pattern>

    后缀名匹配

    错误:不要/打头

    <url-pattern>/admin/*.jsp</url-pattern>

    正确:

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

    只关心是否匹配,不关心资源是否存在。

    注意,拦截页面和拦截servlet的地址不完全一致:

    <!-- 拦截页面--> <url-pattern>/pages/manager/*</url-pattern> <!-- 拦截servlet--> <url-pattern>/manager/bookServlet</url-pattern>

    ThreadLocal类

    该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过get/set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程(例如,用户ID或事务ID)相关联。

    特点

    每个ThreadLocal可以为当前线程关联一个数据。每一个ThreadLocal对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal对象实例。每个ThreadLocal对象实例定义时,一般都是staticThreadLocal中保存数据。在线程销毁后,会由JVM自动释放

    ThreadLocal测试

    package com.bookstore.filter.threadlocal; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; public class ThreadLocalTest { public static Map<String ,Object> data = new ConcurrentHashMap<>(); public static ThreadLocal<Object> threadLocal = new ThreadLocal<>(); private static Random random = new Random(); public static void main(String[] args) { Task task = new Task(); for (int i = 0; i < 3; i++) { new Thread(task,String.valueOf(i)).start(); } } public static class Task implements Runnable{ @Override public void run() { // 在run方法中,随机生成一个变量 Integer i = random.nextInt(1000); // 获取当前线程名 String name = Thread.currentThread().getName(); System.out.println("Thread["+name+"]生成的随机数是"+i); // data.put(name,i); //==> 用ThreadLocal实现 threadLocal.set(i); new OrderService().createOrder(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // System.out.println("Thread["+name+"]生成的随机数是"+data.get(name)); System.out.println("Thread["+name+"]生成的随机数是"+threadLocal.get()); } } } package com.bookstore.filter.threadlocal; public class OrderService { public void createOrder(){ String name = Thread.currentThread().getName(); // System.out.println("当前线程"+name+"中保存的数据是" + ThreadLocalTest.data.get(name)); System.out.println("当前线程"+name+"中保存的数据是" + ThreadLocalTest.threadLocal.get()); } }

    Filter和ThreadLocal实现数据库的事务管理

    要确保所有操作都成功,要么都失败,就必须使用数据库的事务。要确保所有操作都在一个事务内,就必须要确保,所有操作,都使用同一个Connection对象

    解决同一个Connection对象:

    用ThredLocal确保所有操作都使用同一个Connection对象。 前提:所有操作都是在同一线程中。回滚事务需要得知异常,也就是内部操作将异常的处理权 让渡 给数据库事务管理的代码。

    数据库事务(Jdbc实现):

    @Test public void testCommit() throws SQLException { Connection connection = JdbcUtils.getConnection(); //conn.set(connection); try{ connection.setAutoCommit(false); //connection = conn.get(); //执行一系列的jdbc操作 connection.commit();//connection = conn.get(); } catch (SQLException e) { connection.rollback();//connection = conn.get(); }finally { JdbcUtils.close(connection);//connection = conn.get(); } //======================================== //改写后 try{ //执行一系列的jdbc操作 orderService.createOrder(cart,userId);底层调用了多步对数据库的操作 JdbcUtils.commitAndClose();//提交事务 } catch (SQLException e) { JdbcUtils.rollbackAndClose();//回滚事务 e.printStackTrace(); } }

    改写数据库connection的相关操作改写:

    /* * 为了数据库事务 提交/回滚 改写的 */ private static ThreadLocal<Connection> conn = new ThreadLocal<>(); public static Connection getConnection(){ Connection connection = conn.get(); if (connection == null){ try { connection = dataSource.getConnection(); conn.set(connection); //保存到ThreadLocal对象中,供后面的jdbc使用 connection.setAutoCommit(false);//设置为手动管理 } catch (SQLException e) { e.printStackTrace(); } } return connection; } /* * 提交事务,并关闭释放连接 */ public static void commitAndClose(){ Connection connection = conn.get(); if (connection!=null){ try { connection.commit();//提交事务 } catch (SQLException e) { e.printStackTrace(); }finally { try { connection.close();//关闭连接 } catch (SQLException e) { e.printStackTrace(); } } } //一定要执行remove操作,否则会出错,因为tomcat'服务器使用了线程池技术 conn.remove(); } /* * 回滚事务,并关闭释放连接 */ public static void rollbackAndClose(){ Connection connection = conn.get(); if (connection!=null){ try { connection.rollback();//提交事务 } catch (SQLException e) { e.printStackTrace(); }finally { try { connection.close();//关闭连接 } catch (SQLException e) { e.printStackTrace(); } } } //一定要执行remove操作,否则会出错,因为tomcat'服务器使用了线程池技术 conn.remove(); } 最后,将数据库事务操作改写成了 try{ //执行一系列的jdbc操作 orderService.createOrder(cart,userId);底层调用了多步对数据库的操作 JdbcUtils.commitAndClose();//提交事务 } catch (SQLException e) { JdbcUtils.rollbackAndClose();//回滚事务 e.printStackTrace(); }

    使用Filter给所有的service方法加上try-catch

    上面是对项目其中一个操作的改写,但项目中往往有多个操作,也就是要改写多处代码。

    可以用Filter,相当于给所有的service方法加上try-catch但若是过程中,依然有try-catch把异常内部消化了,那么TransactionFilter还是接收不到异常,影响对事务成功还是失败的判断。但是这样统一处理异常,缺乏错误页面的展示 <!-- 配置过滤器--> <filter> <filter-name>TransactionFilter</filter-name> <filter-class>com.bookstore.filter.TransactionFilter</filter-class> </filter> <filter-mapping> <filter-name>TransactionFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> package com.bookstore.filter; import com.bookstore.utils.JdbcUtils; import javax.servlet.*; import java.io.IOException; 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 (IOException e) { JdbcUtils.commitAndClose(); } catch (ServletException e) { e.printStackTrace(); e.printStackTrace(); } } }

    Tomcat对异常统一管理

    编写一些错误页面。在web.xml做如下配置在Filter中重新抛出异常 newRuntimeException(e); <error-page> <error-code>500</error-code> <location>/pages/error/error500.jsp</location> </error-page>
    Processed: 0.012, SQL: 9