Java三十五篇:初识Filter
Filter概述
Filter是JavaWeb三大组件之一。
Filter是JavaEE的规范。
Filter作用是拦截请求,过滤响应。拦截请求常用的应用场景
权限检查
日志记录
事务管理、等
Filter 的工作流程图:

Filter开始之前的体验
要求:
在web工程目录下有一个admin文件夹,这个文件夹的资源必须在登陆之后才能允许访问。
思考:
1.如何检查用户是否登陆?
由于当用于登陆后,信息会被保留到session中。所以查看sessino中是否有用户登陆信息即可。
实现:
在jsp文件中加入西面代码。
<%
Object user = session.getAttribute("user");
//判断用户是否为空
if(user == null){
request.getRequestDispatcher("/login.jsp").forward(request,response);
return ;
}
%>
问题:
由于上面代码是java代码,html中不能写,所以这种方法智能针对jsp文件进行设定。
那么html文件怎么办呢?那么Filter就登场了。
Filter过滤器的使用
创建Filter的实现类去实现Filter接口
重写doFilter的方法
到web.xml中配置拦截器的拦截路径 使用Filter实现登录页面的功能。
Filter的生命周期
Filter 的生命周期包含几个方法
1、 构造器方法
2、 init 初始化方法 第 1, 2 步, 在 web 工程启动的时候执行(Filter 已经创建)
3、 doFilter 过滤方法 第 3 步, 每次拦截到请求, 就会执行
4、 destroy 销毁 第 4 步, 停止 web 工程的时候, 就会执行(停止 web 工程, 也会销毁 Filter 过滤器)
FilterConfig类
FilterConfig类概述
FilterConfig就是Filter过滤器的的配置类.
Tomcat启动,创建Filter的的时候就会创建一个FilterConfig类对象,
这里包含了Filter的配置信息。
FilterConfig类的作用
FilterConfig 类见名知义, 它是 Filter 过滤器的配置文件类。Tomcat 每次创建 Filter 的时候, 也会同时创建一个 FilterConfig 类, 这里包含了 Filter 配置文件的配置信息。
FilterConfig 类的作用是获取 filter 过滤器的配置内容
1、获取过滤器的名称即Filter-name标签的值。
2、获取过滤器的初始参数即init-param标签的值。
3、获取ServletContext对象。
FilterChain过滤链
概述
FilterChain过滤链就是多个过滤器的时候如何执行。
如果不是最后一个过滤器,
则按照web.xml中filter-mapping的定义的先后顺序往下执行。
如果是最后一个过滤器了,则执行目标资源。

作用
往下继续执行过滤器
执行目标资源
当FilterChain过滤器有多个Filter时的特点
多个Filter的执行顺序的先后是由在web.xml中定义的先后(从上到下)顺序决定的。注意:由于Filter是根据URL进行过滤的,最先走到的是filter-mapping标签,所以上面所说的定义的先后顺序是指filter-mapping标签的位置顺序。
多个Filter执行的时候默认是同一个线程目标资源也是同一个线程
执行的时候是同一个request对象。(因为同一次请求)
Filter拦截器路径
精确匹配
例 /target.jsp 表示URL地址必须是:http://服务地址:端口/工程目录/target.jsp
目录匹配
例 /admin/* 表示URL地址必须是以:http://服务地址:端口/工程目录/admin/ 开头
后缀名匹配
● 例1
*.jsp
表示URL地址必须以.jsp结尾
● 例2
*.do
表示URL地址必须以.do结尾 注:不能是含有/否则错误
Filter 过滤器它只关心请求的地址是否匹配, 不关心请求的资源是否存在!!!
1、 ThreadLocal 的使用
ThreadLocal 的作用, 它可以解决多线程的数据安全问题。
ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量, 可以是对象, 也可以是数组, 集合)
ThreadLocal 的特点:
1、 ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据, key 为当前线程)
2、 每一个 ThreadLocal 对象, 只能为当前线程关联一个数据, 如果要为当前线程关联多个数据, 就需要使用多个ThreadLocal 对象实例。
3、 每个 ThreadLocal 对象实例定义的时候, 一般都是 static 类型
4、 ThreadLocal 中保存数据, 在线程销毁后。会由 JVM 虚拟自动释放。测试类:
publicclass OrderService {
public void createOrder() {
String name = Thread.currentThread().getName();
System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" +
ThreadLocalTest.threadLocal.get());
new OrderDao().saveOrder();
}
}
publicclass OrderDao {
public void saveOrder() {
String name = Thread.currentThread().getName();
System.out.println("OrderDao 当前线程[" + name + "]中保存的数据是:" +
ThreadLocalTest.threadLocal.get());
}
}
publicclass ThreadLocalTest {
// public static Map<String,Object> data = new Hashtable<String,Object>();
publicstatic ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
privatestatic Random random = new Random();
publicstaticclass 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();
}
}
}
}
3、 使用 Filter 和 ThreadLocal 组合管理事务
3.1、 使用 ThreadLocal 来确保所有 dao 操作都:在同一个 Connection 连接对象中完 成
原理分析图:

JdbcUtils 工具类的修改:修 改 BaseDao
publicclass JdbcUtils {
privatestatic DruidDataSource dataSource;
privatestatic 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();
}
/**
*关闭连接,放回数据库连接池
*@param conn
**/
public static void close(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
修改 BaseDao
publicabstractclass BaseDao {
//使用 DbUtils 操作数据库
private QueryRunner queryRunner = new QueryRunner();
/**
* update() 方法用来执行:Insert\Update\Delete 语句
* *
@return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
*/
public int update(String sql, Object... args) {
System.out.println(" BaseDao 程序在[" + Thread.currentThread().getName() + "]中");
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.update(connection, sql, args);
} catch (SQLException e) {
e.printStackTrace();
thrownew RuntimeException(e);
}
}
/**
* 查询返回一个 javaBean 的 sql 语句
* *
@param type 返回的对象类型
* @param sql 执行的 sql 语句
* @param args sql 对应的参数值
* @param <T> 返回的类型的泛型
* @return
*/
public <T> T queryForOne(Class<T> type, String sql, Object... args) {
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con, sql, new BeanHandler<T>(type), args);
} catch (SQLException e) {
e.printStackTrace();
thrownew RuntimeException(e);
}
}
/**
* 查询返回多个 javaBean 的 sql 语句
* *
@param type 返回的对象类型
* @param sql 执行的 sql 语句
* @param args sql 对应的参数值
* @param <T> 返回的类型的泛型
* @return
*/
public <T> List<T> queryForList(Class<T> type, String sql, Object... args) {
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con, sql, new BeanListHandler<T>(type), args);
} catch (SQLException e) {
e.printStackTrace();
thrownew RuntimeException(e);
}
}
/**
* 执行返回一行一列的 sql 语句
* @param sql 执行的 sql 语句
* @param args sql 对应的参数值
* @return
*/
public Object queryForSingleValue(String sql, Object... args) {
Connection conn = JdbcUtils.getConnection();
try {
return queryRunner.query(conn, sql, new ScalarHandler(), args);
} catch (SQLException e) {
e.printStackTrace();
thrownew RuntimeException(e);
}
}
}
}
3.2、 使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch。来进行实现的 管理。
原理分析图 :

Filter 类代码:
publicclass 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>com.atguigu.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!-- /* 表示当前工程下所有请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
一定要记得把 BaseServlet 中的异常往外抛给 Filter 过滤器
publicabstractclass BaseServlet extends HttpServlet {
@Override
protected 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 = req.getParameter("action");
try {
// 获取 action 业务鉴别字符串, 获取相应的业务 方法反射对象
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class,
HttpServletResponse.class);
// System.out.println(method);
// 调用目标业务 方法
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
thrownew RuntimeException(e);// 把异常抛给 Filter 过滤器
}
}
}
3.3、 将所有异常都统一交给 Tomcat, 让 Tomcat 展示友好的错误信息页面
在 web.xml 中我们可以通过错误页面配置来进行管理。
<!--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>
