Java Servlet过滤器链原理与请求/响应包装器实战指南
1. Servlet过滤器概述
Servlet过滤器是Java Web开发中的核心组件,它能够在请求到达Servlet之前和响应返回客户端之前对数据进行预处理和后处理。过滤器通过链式调用的方式组成过滤器链,每个过滤器负责特定的功能,如身份验证、日志记录、数据压缩等。
1.1 过滤器的工作原理
```java
public class LoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化代码
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 预处理逻辑
System.out.println("请求到达时间: " + new Date());
// 调用过滤器链中的下一个过滤器
chain.doFilter(request, response);
// 后处理逻辑
System.out.println("响应返回时间: " + new Date());
}
@Override
public void destroy() {
// 清理资源
}
}
```
2. 过滤器链执行机制
2.1 链式调用原理
过滤器链采用责任链模式,按照在web.xml中配置的顺序依次执行:
请求 → Filter1 → Filter2 → ... → FilterN → Servlet → FilterN后处理 → ... → Filter1后处理 → 响应
2.2 配置示例
```xml
LoggingFilter
com.example.LoggingFilter
LoggingFilter
/
AuthFilter
com.example.AuthFilter
AuthFilter
/secure/
```
3. 请求包装器实战
3.1 自定义请求包装器
请求包装器允许我们修改或增强请求参数,常用于参数验证、数据脱敏等场景。
```java
public class CustomRequestWrapper extends HttpServletRequestWrapper {
private final Map modifiedParameters;
public CustomRequestWrapper(HttpServletRequest request) {
super(request);
this.modifiedParameters = new HashMap<>(request.getParameterMap());
}
// 重写获取参数的方法,实现参数过滤
@Override
public String getParameter(String name) {
String[] values = modifiedParameters.get(name);
if (values != null && values.length > 0) {
return sanitize(values[0]); // 参数净化
}
return null;
}
// 添加新参数
public void setParameter(String name, String value) {
modifiedParameters.put(name, new String[]{value});
}
// 参数净化处理
private String sanitize(String input) {
return input.replaceAll("<script>", "").trim();
}
@Override
public Map<String, String[]> getParameterMap() {
return Collections.unmodifiableMap(modifiedParameters);
}
}
```
3.2 在过滤器中使用请求包装器
```java
public class XSSFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
CustomRequestWrapper wrappedRequest = new CustomRequestWrapper(httpRequest);
// 对特定参数进行预处理
if (wrappedRequest.getParameter("content") != null) {
wrappedRequest.setParameter("filteredContent",
"已过滤: " + wrappedRequest.getParameter("content"));
}
chain.doFilter(wrappedRequest, response);
}
}
```
4. 响应包装器实战
4.1 自定义响应包装器
响应包装器用于修改响应内容,常用于数据压缩、内容替换、响应日志等场景。
```java
public class CompressionResponseWrapper extends HttpServletResponseWrapper {
private GZIPServletOutputStream gzipStream;
private PrintWriter printWriter;
private ByteArrayOutputStream buffer;
public CompressionResponseWrapper(HttpServletResponse response) {
super(response);
this.buffer = new ByteArrayOutputStream();
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (gzipStream == null) {
gzipStream = new GZIPServletOutputStream(buffer);
}
return gzipStream;
}
@Override
public PrintWriter getWriter() throws IOException {
if (printWriter == null) {
printWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(),
getCharacterEncoding()));
}
return printWriter;
}
// 获取压缩后的数据
public byte[] getCompressedData() throws IOException {
if (gzipStream != null) {
gzipStream.close();
}
if (printWriter != null) {
printWriter.close();
}
return buffer.toByteArray();
}
}
// 自定义GZIP输出流
class GZIPServletOutputStream extends ServletOutputStream {
private final GZIPOutputStream gzipStream;
public GZIPServletOutputStream(OutputStream output) throws IOException {
this.gzipStream = new GZIPOutputStream(output);
}
@Override
public void write(int b) throws IOException {
gzipStream.write(b);
}
@Override
public void close() throws IOException {
gzipStream.close();
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener listener) {
// 实现省略
}
}
```
4.2 响应内容修改过滤器
```java
public class ContentModificationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
CompressionResponseWrapper wrappedResponse =
new CompressionResponseWrapper(httpResponse);
// 设置响应头,支持gzip压缩
httpResponse.setHeader("Content-Encoding", "gzip");
chain.doFilter(request, wrappedResponse);
// 获取压缩后的数据并写入响应
byte[] compressedData = wrappedResponse.getCompressedData();
response.getOutputStream().write(compressedData);
}
}
```
5. 综合实战案例:API日志记录过滤器
下面是一个完整的实战示例,展示如何记录API请求和响应的详细信息。
```java
public class ApiLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ApiLoggingFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
long startTime = System.currentTimeMillis();
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 创建自定义请求包装器记录请求体
BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(httpRequest);
// 创建自定义响应包装器记录响应体
BufferedResponseWrapper bufferedResponse = new BufferedResponseWrapper(httpResponse);
try {
// 记录请求信息
logRequestInfo(bufferedRequest);
// 继续过滤器链
chain.doFilter(bufferedRequest, bufferedResponse);
} finally {
// 记录响应信息
long duration = System.currentTimeMillis() - startTime;
logResponseInfo(bufferedRequest, bufferedResponse, duration);
// 将缓冲的响应内容写入实际响应
byte[] responseData = bufferedResponse.getResponseData();
if (responseData.length > 0) {
response.getOutputStream().write(responseData);
}
}
}
private void logRequestInfo(BufferedRequestWrapper request) throws IOException {
StringBuilder logMsg = new StringBuilder();
logMsg.append("REQUEST: ").append(request.getMethod())
.append(" ").append(request.getRequestURI())
.append("\nHeaders: ").append(getHeadersInfo(request))
.append("\nBody: ").append(request.getRequestBody());
logger.info(logMsg.toString());
}
private void logResponseInfo(HttpServletRequest request,
BufferedResponseWrapper response, long duration) {
StringBuilder logMsg = new StringBuilder();
logMsg.append("RESPONSE: ").append(request.getRequestURI())
.append(" - Status: ").append(response.getStatus())
.append(" - Duration: ").append(duration).append("ms")
.append("\nBody: ").append(response.getResponseContent());
logger.info(logMsg.toString());
}
private String getHeadersInfo(HttpServletRequest request) {
Enumeration<String> headerNames = request.getHeaderNames();
StringBuilder headers = new StringBuilder();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.append(headerName).append(": ")
.append(request.getHeader(headerName)).append("; ");
}
return headers.toString();
}
}
```
6. 最佳实践与注意事项
6.1 性能考虑
- 避免在过滤器中执行耗时操作
- 合理使用缓冲区,防止内存溢出
- 考虑使用异步处理提高性能
6.2 异常处理
```java
public class ExceptionHandlingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception e) {
// 统一异常处理
handleException(e, (HttpServletResponse) response);
}
}
private void handleException(Exception e, HttpServletResponse response) {
// 实现异常处理逻辑
}
}
```
6.3 配置优化建议
- 合理安排过滤器执行顺序
- 使用@WebFilter注解简化配置
- 考虑使用Spring Security等框架增强安全性
7. 总结
Servlet过滤器链和请求/响应包装器是Java Web开发中强大的工具,它们提供了非侵入式的请求/响应处理能力。通过合理运用这些技术,我们可以实现统一的安全控制、日志记录、性能监控等功能,同时保持代码的清晰和可维护性。
在实际项目中,建议根据具体需求选择合适的包装器策略,并注意性能优化和异常处理,以确保系统的稳定性和高效性。随着微服务架构的流行,这些技术在API网关、服务网格等场景中仍有重要的应用价值。
1110

被折叠的 条评论
为什么被折叠?



