文章目录
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对象实例定义时,一般都是static
- ThreadLocal中保存数据。在线程销毁后,会由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>