Filter
在 Java Web 开发中,Filter(过滤器)是 Servlet 规范中的一个重要组件,它可以对客户端与服务器之间的请求和响应进行预处理和后处理。以下从多个方面详细介绍 Java Web 中的 Filter:
一、概念和作用
- 概念:Filter 是一种特殊的 Servlet 组件,它可以在请求到达目标资源(如 Servlet、JSP 等)之前或响应返回客户端之前对请求和响应进行拦截和处理。
- 作用
- 权限验证:检查用户是否具有访问某个资源的权限,例如在用户未登录时阻止其访问需要登录才能访问的页面。
- 字符编码处理:统一设置请求和响应的字符编码,避免出现乱码问题。
- 日志记录:记录每个请求的详细信息,如请求的 URL、参数、请求时间等,方便后续的系统监控和问题排查。
- 数据过滤和验证:对请求参数进行过滤和验证,防止恶意输入或不符合要求的数据进入系统。
二、工作原理
当客户端发送请求到服务器时,请求首先会经过一系列的 Filter。每个 Filter 可以对请求进行检查和修改,然后将请求传递给下一个 Filter 或目标资源。当目标资源处理完请求后,响应会按照相反的顺序再次经过这些 Filter,每个 Filter 可以对响应进行后处理,最后将响应返回给客户端。
实现步骤
1. 创建 Filter 类
Filter 类需要实现 javax.servlet.Filter
接口,并重写其中的三个方法:init()
、doFilter()
和 destroy()
。
import javax.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化方法,在 Filter 实例创建后调用,可用于初始化资源
System.out.println("MyFilter 初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 过滤方法,对请求和响应进行处理
System.out.println("MyFilter 开始处理请求");
// 设置请求和响应的字符编码
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 将请求和响应传递给下一个 Filter 或目标资源
chain.doFilter(request, response);
System.out.println("MyFilter 处理响应结束");
}
@Override
public void destroy() {
// 销毁方法,在 Filter 实例销毁前调用,可用于释放资源
System.out.println("MyFilter 销毁");
}
}
2. 配置 Filter
有两种常见的配置方式:使用 web.xml
配置文件或使用注解。
使用 web.xml
配置
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.example.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<!-- 过滤所有请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
用注解配置
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
// 省略方法实现
}
三、Filter 链
在 Java Web 开发中,过滤器链(Filter Chain)是一个重要的概念,它允许多个过滤器(Filter)按照特定顺序对请求和响应进行处理。下面将从过滤器链的概念、工作原理、配置方式、执行顺序以及应用场景等方面进行详细解释。
概念
过滤器链是由多个过滤器组成的一个有序列表。当客户端向服务器发送请求时,请求会依次经过过滤器链中的每个过滤器,每个过滤器可以对请求进行预处理,如验证用户身份、设置字符编码等。当请求到达目标资源(如 Servlet、JSP 等)并处理完成后,响应会按照相反的顺序再次经过过滤器链,每个过滤器可以对响应进行后处理,如压缩响应数据、添加响应头信息等。
工作原理
- 请求阶段:客户端发送请求到服务器,服务器接收到请求后,会根据配置的过滤器链,依次将请求传递给链中的每个过滤器。每个过滤器在接收到请求后,可以对请求进行修改或增强,然后将请求传递给下一个过滤器。如果某个过滤器决定不将请求继续传递给下一个过滤器,它可以直接返回响应给客户端,从而终止请求的处理流程。
- 响应阶段:当请求到达目标资源并处理完成后,服务器会生成响应。响应会按照与请求阶段相反的顺序经过过滤器链,每个过滤器可以对响应进行修改或增强,最后将响应返回给客户端。
配置方式
可以通过 web.xml
配置文件或注解的方式来配置过滤器链。
使用 web.xml
配置
<filter>
<filter-name>Filter1</filter-name>
<filter-class>com.example.Filter1</filter-class>
</filter>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>com.example.Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Filter2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在上述配置中,Filter1
和 Filter2
会组成一个过滤器链,请求会先经过 Filter1
,再经过 Filter2
。
使用注解配置
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*", filterName = "Filter1")
public class Filter1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 预处理逻辑
System.out.println("Filter1: 请求预处理");
chain.doFilter(request, response);
// 后处理逻辑
System.out.println("Filter1: 响应后处理");
}
}
@WebFilter(urlPatterns = "/*", filterName = "Filter2")
public class Filter2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 预处理逻辑
System.out.println("Filter2: 请求预处理");
chain.doFilter(request, response);
// 后处理逻辑
System.out.println("Filter2: 响应后处理");
}
}
在上述代码中,Filter1
和 Filter2
都会对所有请求进行拦截,具体的执行顺序可能因 Servlet 容器而异。
执行顺序
web.xml
配置:在web.xml
中,<filter-mapping>
标签的配置顺序决定了过滤器的执行顺序。先配置的过滤器会先执行,后配置的过滤器后执行。- 注解配置:使用注解配置时,不同的 Servlet 容器可能有不同的处理方式。有些容器会按照类名的字典序来决定过滤器的执行顺序,而有些容器可能没有明确的顺序。为了确保过滤器按照预期的顺序执行,建议使用
web.xml
进行配置。
应用场景
- 身份验证和授权:可以使用多个过滤器来实现不同级别的身份验证和授权。例如,一个过滤器用于验证用户是否已登录,另一个过滤器用于验证用户是否具有访问特定资源的权限。
- 字符编码处理:可以在过滤器链中配置一个过滤器,用于统一设置请求和响应的字符编码,避免出现乱码问题。
- 日志记录:可以使用过滤器链来记录每个请求的详细信息,如请求的 URL、参数、请求时间等,方便后续的系统监控和问题排查。
- 数据过滤和验证:可以在过滤器链中配置多个过滤器,对请求参数进行过滤和验证,防止恶意输入或不符合要求的数据进入系统。
示例代码执行结果分析
假设有上述的 Filter1
和 Filter2
两个过滤器,当客户端发送一个请求时,控制台可能会输出以下信息:
Filter1: 请求预处理
Filter2: 请求预处理
(目标资源处理请求)
Filter2: 响应后处理
Filter1: 响应后处理
从输出结果可以看出,请求阶段过滤器按照 Filter1 -> Filter2
的顺序执行,响应阶段过滤器按照 Filter2 -> Filter1
的顺序执行。
通过使用过滤器链,可以将不同的功能逻辑分离到不同的过滤器中,提高代码的可维护性和可扩展性。
四、应用场景举例
登录验证 Filter
package com.itheima.web.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/*")
public class LoginFilter implements Filter {
// 未登录即可放行的资源列表,常量
private static final String[] PUBLIC_URLS = {
"/adminServlet","index.html", "admin.jsp", "register.jsp", "/registerServlet", "login.jsp", "/loginServlet"
};
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 设置请求和响应的字符编码,避免中文乱码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
HttpServletRequest req = (HttpServletRequest) request;
// 获取请求的 URI 部分
String uri = req.getRequestURI();
// 检查是否为未登录即可放行的资源
for (String u : PUBLIC_URLS) {
if (uri.endsWith(u)) {
// 可以放行
chain.doFilter(request, response);
return;
}
}
// 到这步说明访问的资源不可以直接放行,必须登录
HttpSession session = req.getSession();
Object user = session.getAttribute("user");
Object admin = session.getAttribute("admin");
// 判断 user 或 admin 是否不为 null
if (user != null || admin != null) {
// 用户或管理员已登录,放行请求
chain.doFilter(request, response);
} else {
// 没登陆过,跳转到登录页面
req.setAttribute("login_errmsg", "请登录");
req.getRequestDispatcher("login.jsp").forward(request, response);
}
}
@Override
public void destroy() {
}
}
这个 Filter 会检查用户是否已登录,如果未登录则重定向到登录页面。