免责声明


由于传播、利用本公众号绿洲安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号绿洲安全及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢
转载:https://blog.csdn.net/m0_73803300/article/details/151397318一
过滤器基础
过滤器(Filter) 是 JavaWeb 中的一种组件,它可以拦截对 Web 资源(Servlet、JSP、HTML 等)的请求和响应,实现对请求和响应的预处理和后处理。
过滤器的核心功能:
在请求到达目标资源前,检查或修改请求数据
在响应返回客户端前,检查或修改响应数据
决定是否允许请求到达目标资源(权限控制)
对请求和响应进行日志记录、数据压缩等通用处理
过滤器工作原理
过滤器的工作流程如下:
1.客户端发送请求到服务器
2.请求首先经过过滤器链(多个过滤器按顺序执行)
3.过滤器可以决定:
1.放行请求(调用chain.doFilter(request, response))
2.中断请求(不调用doFilter方法,直接返回响应)
3.修改请求数据后再放行
4.请求到达目标资源(Servlet/JSP 等)
5.目标资源处理请求,生成响应
6.响应按相反顺序经过过滤器链
7.响应最终返回给客户端
二
过滤器生命周期
过滤器的生命周期包含以下阶段:
初始化(init):Web 应用启动时,服务器创建过滤器实例并调用init(FilterConfig)方法,初始化过滤器
过滤(doFilter):每次请求匹配过滤器的 URL 模式时,服务器调用doFilter(ServletRequest, ServletResponse, FilterChain)方法
销毁(destroy):Web 应用关闭时,服务器调用destroy()方法,释放过滤器占用的资源
三
过滤器核心 API
主要接口和类
javax.servlet.Filter 接口:
void init(FilterConfig filterConfig):初始化方法void doFilter(ServletRequest request, ServletResponse response, FilterChain chain):过滤方法void destroy():销毁方法
javax.servlet.FilterConfig 接口:
String getFilterName():获取过滤器名称ServletContext getServletContext():获取 ServletContext 对象String getInitParameter(String name):获取初始化参数Enumeration<String> getInitParameterNames():获取所有初始化参数名称
javax.servlet.FilterChain 接口:
void doFilter(ServletRequest request, ServletResponse response):将请求传递给下一个过滤器或目标资源四
过滤器配置方式
过滤器可以通过注解或XML两种方式配置:
1. 注解配置
@WebFilter(filterName = "MyFilter", // 过滤器名称urlPatterns = {"/admin/*", "/user/*"}, // 拦截的URL模式initParams = { // 初始化参数@WebInitParam(name = "param1", value = "value1"),@WebInitParam(name = "param2", value = "value2")},dispatcherTypes = { // 拦截的请求类型DispatcherType.REQUEST, // 直接请求DispatcherType.FORWARD, // 转发请求DispatcherType.INCLUDE, // 包含请求DispatcherType.ERROR // 错误页面请求})public class MyFilter implements Filter {// 实现接口方法...}
2. XML 配置(web.xml)
<filter><filter-name>MyFilter</filter-name><filter-class>com.example.filter.MyFilter</filter-class><init-param><param-name>param1</param-name><param-value>value1</param-value></init-param><init-param><param-name>param2</param-name><param-value>value2</param-value></init-param></filter><filter-mapping><filter-name>MyFilter</filter-name><!-- 拦截的URL模式 --><url-pattern>/admin/*</url-pattern><url-pattern>/user/*</url-pattern><!-- 拦截的请求类型 --><dispatcher>REQUEST</dispatcher><dispatcher>FORWARD</dispatcher></filter-mapping>
五
过滤器高级应用场景
1. 统一字符编码过滤器
import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.annotation.WebInitParam;import java.io.IOException;/*** 统一字符编码过滤器* 解决请求参数和响应输出的中文乱码问题*/(urlPatterns = "/*", // 拦截所有请求initParams = {(name = "encoding", value = "UTF-8") // 默认编码})public class CharacterEncodingFilter implements Filter {private String encoding; // 字符编码public void init(FilterConfig filterConfig) throws ServletException {// 获取初始化参数,若未配置则使用默认值encoding = filterConfig.getInitParameter("encoding");if (encoding == null || encoding.trim().isEmpty()) {encoding = "UTF-8";}}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 设置请求编码request.setCharacterEncoding(encoding);// 设置响应编码(两种方式)// 方式1:直接设置响应编码response.setCharacterEncoding(encoding);// 方式2:设置Content-Type,同时指定编码response.setContentType("text/html;charset=" + encoding);// 放行请求chain.doFilter(request, response);}public void destroy() {// 无需释放资源}}
工作原理:
对所有请求(urlPatterns = "/*")设置统一的字符编码
通过初始化参数灵活配置编码方式(默认为 UTF-8)
同时处理请求参数编码和响应输出编码
只需配置一次,即可应用于整个 Web 应用
2. 登录验证与权限控制过滤器
在 Web 应用中,通常需要限制未登录用户访问某些资源,或根据用户角色限制访问权限。
import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;/*** 登录验证与权限控制过滤器* 限制未登录用户访问受保护资源,检查用户权限*/(urlPatterns = {"/admin/*", "/user/*"})public class AuthenticationFilter implements Filter {public void init(FilterConfig filterConfig) throws ServletException {// 初始化操作(如加载权限配置)}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 转换为HttpServletRequest和HttpServletResponseHttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;// 获取请求URIString uri = httpRequest.getRequestURI();String contextPath = httpRequest.getContextPath();String path = uri.substring(contextPath.length());// 1. 检查是否为公开资源(无需登录即可访问)if (isPublicResource(path)) {chain.doFilter(request, response);return;}// 2. 检查用户是否登录HttpSession session = httpRequest.getSession(false);if (session == null || session.getAttribute("user") == null) {// 未登录,重定向到登录页,并记录当前URL用于登录后跳转httpResponse.sendRedirect(contextPath + "/login.jsp?redirect=" +java.net.URLEncoder.encode(path, "UTF-8"));return;}// 3. 检查用户权限User user = (User) session.getAttribute("user");if (!hasPermission(user, path)) {// 无权限,转发到无权限提示页httpRequest.setAttribute("message", "您没有权限访问该资源");httpRequest.getRequestDispatcher("/noPermission.jsp").forward(request, response);return;}// 4. 验证通过,放行chain.doFilter(request, response);}/*** 判断是否为公开资源*/private boolean isPublicResource(String path) {// 登录页、注册页、静态资源等无需登录即可访问return path.startsWith("/login.jsp") ||path.startsWith("/register.jsp") ||path.startsWith("/loginServlet") ||path.startsWith("/registerServlet") ||path.startsWith("/css/") ||path.startsWith("/js/") ||path.startsWith("/images/");}/*** 检查用户是否有权限访问资源*/private boolean hasPermission(User user, String path) {// 管理员可以访问所有资源if ("admin".equals(user.getRole())) {return true;}// 普通用户只能访问/user/*下的资源if ("user".equals(user.getRole()) && path.startsWith("/user/")) {return true;}// 其他情况无权限return false;}public void destroy() {// 释放资源}}
区分公开资源和受保护资源 检查用户登录状态,未登录则重定向到登录页 记录跳转前的 URL,登录后可返回原页面 基于用户角色(管理员 / 普通用户)的权限控制 无权限时显示友好提示
3. 访问日志过滤器
import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;/*** 访问日志过滤器* 记录用户访问的资源、时间、IP等信息*/public class AccessLogFilter implements Filter {private SimpleDateFormat sdf;public void init(FilterConfig filterConfig) throws ServletException {// 初始化日期格式化器sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) request;// 记录请求开始时间long startTime = System.currentTimeMillis();try {// 放行请求chain.doFilter(request, response);} finally {// 无论请求处理成功与否,都记录日志recordLog(httpRequest, startTime);}}/*** 记录访问日志*/private void recordLog(HttpServletRequest request, long startTime) {try {// 计算请求处理时间long endTime = System.currentTimeMillis();long costTime = endTime - startTime;// 获取日志信息String timestamp = sdf.format(new Date(startTime)); // 访问时间String ip = getClientIp(request); // 客户端IPString method = request.getMethod(); // 请求方法String uri = request.getRequestURI(); // 请求URIString queryString = request.getQueryString(); // 查询参数String userAgent = request.getHeader("User-Agent"); // 浏览器信息String username = getUsername(request); // 登录用户名// 构建日志内容StringBuilder log = new StringBuilder();log.append("[").append(timestamp).append("] ");log.append("IP:").append(ip).append(" ");log.append("User:").append(username).append(" ");log.append(method).append(" ").append(uri);if (queryString != null) {log.append("?").append(queryString);}log.append(" ").append("耗时:").append(costTime).append("ms ");log.append("UA:").append(userAgent);// 输出日志(实际应用中可写入文件或数据库)System.out.println(log.toString());// TODO: 实际应用中可将日志保存到数据库// saveLogToDatabase(timestamp, ip, username, method, uri, queryString, costTime, userAgent);} catch (Exception e) {System.err.println("记录访问日志失败: " + e.getMessage());}}/*** 获取客户端真实IP(考虑代理情况)*/private String getClientIp(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}// 多级代理情况下,取第一个IPif (ip != null && ip.contains(",")) {ip = ip.split(",")[0].trim();}return ip;}/*** 获取登录用户名*/private String getUsername(HttpServletRequest request) {Object user = request.getSession(false).getAttribute("user");if (user != null && user instanceof User) {return ((User) user).getUsername();}return "未登录";}public void destroy() {// 释放资源}}
日志记录内容:
访问时间和处理耗时
客户端 IP 地址(支持代理场景)
请求方法(GET/POST 等)和请求地址
登录用户名(未登录则标记为 "未登录")
浏览器信息(User-Agent)
处理耗时(用于性能分析)
4. XSS 攻击防护过滤器
XSS(Cross-Site Scripting,跨站脚本攻击)是一种常见的 Web 安全漏洞,攻击者通过注入恶意脚本危害用户数据安全。
import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import java.io.IOException;/*** XSS攻击防护过滤器* 过滤请求参数中的恶意脚本*/(urlPatterns = "/*")public class XssFilter implements Filter {public void init(FilterConfig filterConfig) throws ServletException {// 初始化操作}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 使用包装类处理请求参数chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);}public void destroy() {// 释放资源}/*** 自定义HttpServletRequest包装类,用于过滤XSS攻击代码*/private static class XssHttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {public XssHttpServletRequestWrapper(HttpServletRequest request) {super(request);}/*** 重写getParameter方法,过滤参数值*/public String getParameter(String name) {String value = super.getParameter(name);return value == null ? null : cleanXSS(value);}/*** 重写getParameterValues方法,过滤参数值数组*/public String[] getParameterValues(String name) {String[] values = super.getParameterValues(name);if (values != null) {for (int i = 0; i < values.length; i++) {values[i] = cleanXSS(values[i]);}}return values;}/*** 重写getAttribute方法*/public Object getAttribute(String name) {Object value = super.getAttribute(name);if (value instanceof String) {return cleanXSS((String) value);}return value;}/*** 清理XSS攻击代码*/private String cleanXSS(String value) {if (value == null || value.isEmpty()) {return value;}// 移除危险HTML标签和属性return value// 移除<script>标签.replaceAll("<script>", "<script>").replaceAll("</script>", "</script>")// 移除 onclick, onload 等事件属性.replaceAll("onclick=", "onclick=").replaceAll("onload=", "onload=").replaceAll("onerror=", "onerror=")// 移除javascript:伪协议.replaceAll("javascript:", "javascript:").replaceAll("vbscript:", "vbscript:")// 转义引号,防止闭合属性.replaceAll("\"", """).replaceAll("'", "'");}}}
防护原理:
使用装饰器模式(Wrapper)包装 HttpServletRequest
重写获取参数的方法,对参数值进行过滤
转义 HTML 特殊字符和危险脚本
过滤 JavaScript 伪协议和事件处理器
对所有请求参数进行统一处理,无需在每个 Servlet 中单独处理
5. 压缩响应过滤器
对于文本类响应(HTML、CSS、JavaScript 等),进行 Gzip 压缩可以减少传输数据量,提高页面加载速度。
import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.Enumeration;/*** 响应压缩过滤器* 对文本类型的响应进行Gzip压缩*/public class GzipFilter implements Filter {public void init(FilterConfig filterConfig) throws ServletException {// 初始化操作}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;// 检查浏览器是否支持Gzip压缩String acceptEncoding = httpRequest.getHeader("Accept-Encoding");if (acceptEncoding != null && acceptEncoding.contains("gzip")) {// 创建压缩响应包装类GzipResponseWrapper gzipResponse = new GzipResponseWrapper(httpResponse);try {// 设置Content-Encoding响应头gzipResponse.setHeader("Content-Encoding", "gzip");// 放行请求,使用压缩响应chain.doFilter(request, gzipResponse);} finally {// 完成压缩并写入响应gzipResponse.finish();}} else {// 浏览器不支持Gzip,直接放行chain.doFilter(request, response);}}public void destroy() {// 释放资源}}
对应的 Gzip 响应包装类:
import javax.servlet.ServletOutputStream;import javax.servlet.WriteListener;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpServletResponseWrapper;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.util.zip.GZIPOutputStream;/*** Gzip响应包装类* 用于压缩响应内容*/public class GzipResponseWrapper extends HttpServletResponseWrapper {private ByteArrayOutputStream byteArrayOutputStream;private GZIPOutputStream gzipOutputStream;private PrintWriter printWriter;private ServletOutputStream servletOutputStream;public GzipResponseWrapper(HttpServletResponse response) {super(response);byteArrayOutputStream = new ByteArrayOutputStream();try {gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);} catch (IOException e) {throw new RuntimeException("创建Gzip输出流失败", e);}}/*** 重写getWriter方法,返回PrintWriter*/public PrintWriter getWriter() throws IOException {if (printWriter == null) {printWriter = new PrintWriter(new OutputStreamWriter(gzipOutputStream, getCharacterEncoding()));}return printWriter;}/*** 重写getOutputStream方法,返回ServletOutputStream*/public ServletOutputStream getOutputStream() throws IOException {if (servletOutputStream == null) {servletOutputStream = new GzipServletOutputStream(gzipOutputStream);}return servletOutputStream;}/*** 完成压缩并写入响应*/public void finish() throws IOException {if (printWriter != null) {printWriter.flush();}if (servletOutputStream != null) {servletOutputStream.flush();}// 完成Gzip压缩gzipOutputStream.finish();// 获取压缩后的数据byte[] compressedData = byteArrayOutputStream.toByteArray();// 设置Content-Length响应头getResponse().setContentLength(compressedData.length);// 将压缩后的数据写入原始响应getResponse().getOutputStream().write(compressedData);}/*** Gzip ServletOutputStream实现类*/private static class GzipServletOutputStream extends ServletOutputStream {private GZIPOutputStream gzipOutputStream;public GzipServletOutputStream(GZIPOutputStream gzipOutputStream) {this.gzipOutputStream = gzipOutputStream;}public void write(int b) throws IOException {gzipOutputStream.write(b);}public void flush() throws IOException {gzipOutputStream.flush();}public void close() throws IOException {gzipOutputStream.close();}public boolean isReady() {return true;}public void setWriteListener(WriteListener listener) {// 简化实现}}}
压缩效果:
文本类资源(HTML、CSS、JS 等)压缩率可达 70% 以上
显著减少网络传输数据量,提高页面加载速度
自动检测浏览器是否支持 Gzip 压缩
只对指定类型的资源进行压缩,避免对二进制文件(图片、视频等)无效压缩
六
过滤器链与执行顺序
当多个过滤器同时拦截相同的资源时,会形成过滤器链,过滤器按特定顺序执行。
过滤器执行顺序的确定
1.XML 配置方式:
1.过滤器的执行顺序由<filter-mapping>在 web.xml 中的配置顺序决定
2.先配置的<filter-mapping>对应的过滤器先执行
2.注解配置方式:
1.没有明确的配置顺序,不同容器可能有不同的处理方式
2.通常按过滤器类名的字母顺序执行(不推荐依赖此行为)
3.混合配置方式:
1.XML 配置的过滤器总是先于注解配置的过滤器执行
2.XML 内部按配置顺序,注解内部按类名字母顺序
过滤器链执行示例
假设有三个过滤器:FilterA、FilterB、FilterC,按 A→B→C 的顺序执行:
// FilterApublic void doFilter(...) {System.out.println("FilterA: 请求处理前");chain.doFilter(request, response); // 调用下一个过滤器System.out.println("FilterA: 响应处理后");}// FilterBpublic void doFilter(...) {System.out.println("FilterB: 请求处理前");chain.doFilter(request, response); // 调用下一个过滤器System.out.println("FilterB: 响应处理后");}// FilterCpublic void doFilter(...) {System.out.println("FilterC: 请求处理前");chain.doFilter(request, response); // 调用目标资源System.out.println("FilterC: 响应处理后");}
执行结果:
FilterA: 请求处理前FilterB: 请求处理前FilterC: 请求处理前目标资源处理请求FilterC: 响应处理后FilterB: 响应处理后FilterA: 响应处理后
执行特点:
请求阶段:过滤器按顺序执行(A→B→C)
响应阶段:过滤器按相反顺序执行(C→B→A)
每个过滤器都可以决定是否继续执行过滤器链(调用chain.doFilter())
七
过滤器最佳实践
1. 过滤器设计原则
单一职责:每个过滤器只负责一项功能,提高复用性
配置灵活:通过初始化参数使过滤器更通用
性能优先:避免在过滤器中执行耗时操作
异常处理:妥善处理过滤器中的异常,不影响整个请求
类型转换:需要时将 ServletRequest 转换为 HttpServletRequest 以使用 HTTP 特性
2. 常见问题及解决方案
(1)中文乱码问题
确保字符编码过滤器在所有过滤器中第一个执行
同时设置请求编码(request.setCharacterEncoding())和响应编码
对于 POST 请求:必须设置request.setCharacterEncoding()
对于 GET 请求:Tomcat 等服务器需要在 server.xml 中配置URIEncoding="UTF-8"
(2)过滤器链执行顺序问题
对于需要特定顺序的过滤器,使用 XML 配置而非注解
按功能逻辑排序:如先编码过滤,再登录验证,最后日志记录
在过滤器文档中明确说明依赖关系和建议的执行顺序
(3)响应已提交问题
避免在过滤器中过早写入响应数据
调用chain.doFilter()后,不要再修改响应头
使用响应包装类时,确保正确处理缓冲区
(4)性能问题
避免对静态资源(图片、CSS、JS 等)应用不必要的过滤器
对耗时操作(如数据库访问)进行缓存
合理设置过滤器的 URL 模式,避免过度拦截
3. 过滤器与拦截器的区别
在 Java 生态中,除了 Servlet 过滤器,Spring 等框架还提供了拦截器(Interceptor),两者的主要区别:
| 特性 | 过滤器(Filter) | 拦截器(Interceptor) |
|---|---|---|
| 规范 | Servlet 规范定义 | 框架自行定义(如 Spring) |
| 执行位置 | Web 容器级别 | 框架级别(如 Spring MVC 的 DispatcherServlet 之后) |
| 拦截对象 | 所有 Web 资源 | 主要是控制器(如 Spring MVC 的 Controller) |
| 生命周期 | 由 Web 容器管理 | 由框架容器管理 |
| 功能 | 主要处理请求 / 响应的通用功能 | 主要处理业务相关的拦截(如权限、日志) |
| 灵活性 | 较低,依赖 Servlet API | 较高,可以访问业务对象 |
综合案例:安全的用户管理系统
整合多个过滤器,构建一个安全、高效的用户管理系统:
CharacterEncodingFilter:处理中文编码
XssFilter:防止 XSS 攻击
AuthenticationFilter:登录验证和权限控制
AccessLogFilter:记录访问日志
GzipFilter:压缩响应数据
配置过滤器链(web.xml)
<!-- 1. 字符编码过滤器(最先执行) --><filter><filter-name>characterEncodingFilter</filter-name><filter-class>com.example.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>characterEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 2. XSS防护过滤器 --><filter><filter-name>xssFilter</filter-name><filter-class>com.example.filter.XssFilter</filter-class></filter><filter-mapping><filter-name>xssFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 3. 登录验证过滤器 --><filter><filter-name>authenticationFilter</filter-name><filter-class>com.example.filter.AuthenticationFilter</filter-class></filter><filter-mapping><filter-name>authenticationFilter</filter-name><url-pattern>/admin/*</url-pattern><url-pattern>/user/*</url-pattern></filter-mapping><!-- 4. 访问日志过滤器 --><filter><filter-name>accessLogFilter</filter-name><filter-class>com.example.filter.AccessLogFilter</filter-class></filter><filter-mapping><filter-name>accessLogFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 5. 响应压缩过滤器(最后执行) --><filter><filter-name>gzipFilter</filter-name><filter-class>com.example.filter.GzipFilter</filter-class></filter><filter-mapping><filter-name>gzipFilter</filter-name><url-pattern>*.html</url-pattern><url-pattern>*.jsp</url-pattern><url-pattern>*.css</url-pattern><url-pattern>*.js</url-pattern><url-pattern>*.json</url-pattern></filter-mapping>
执行流程:
首先统一处理字符编码,避免后续处理出现乱码
过滤所有请求参数中的 XSS 攻击代码
验证用户登录状态和访问权限
记录用户的访问日志
对文本响应进行压缩,提高传输效率
八
总结与实践
知识点回顾
过滤器基础:
过滤器是拦截请求和响应的组件,可实现预处理和后处理
核心接口:Filter、FilterConfig、FilterChain
生命周期:init () → doFilter () → destroy ()
高级应用场景:
统一字符编码处理
登录验证与基于角色的权限控制
访问日志记录与审计
XSS 等安全攻击防护
响应数据压缩
过滤器链:
多个过滤器按特定顺序组成过滤器链
请求阶段按顺序执行,响应阶段按逆序执行
XML 配置比注解配置更能保证执行顺序
最佳实践:
单一职责,每个过滤器只处理一项功能
合理设置执行顺序,特别是编码过滤器应最先执行
妥善处理异常,避免影响整个请求流程
针对不同资源类型设置合适的 URL 模式