工具

环境

Filter(过滤器)

FilterSpring中的拦截机制之一,位于Servlet 容器层,作用于 HTTP 请求和响应的生命周期,最早执行,位于拦截器和切面之前.

可以有多个过滤器,多个过滤器时,按 FilterRegistrationBean 的顺序或注册顺序执行

Filter(过滤器).drawio

过滤器可以拿到请求数据ServletRequest(如请求 URL、Headers、Body 等)。响应数据ServletResponse(如设置响应的状态码、Headers 等)。常用于:①请求/响应的日志记录。②安全认证与授权(如检查 Header 的 Token)。③设置编码格式(如 request.setCharacterEncoding("UTF-8"))。④处理跨域请求(CORS)。⑤请求/响应的流操作(如压缩响应内容)。

过滤器的生命周期

  • init(): 初始化Filter 实例,Filter 的生命周期与 Servlet 是相同的,也就是当 Web 容器(tomcat)启动时,调用 init() 方法初始化实例,Filter只会初始化一次。需要设置初始化参数的时候,可以写到init()方法中。

  • doFilter(): 业务处理,拦截要执行的请求,对请求和响应进行处理,一般需要处理的业务操作都在这个方法中实现

  • destroy() : 销毁实例,关闭容器时调用 destroy() 销毁 Filter 的实例。

过滤器的使用

首先实现 javax.servlet.Filter 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import javax.servlet.*;
import java.io.IOException;

/**
* @Author: Lambda
* @Description: 自定义过滤器
* @Date: 2025/1/5 上午11:37
*/
public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化逻辑(可选)
System.out.println("Initializing MyFilter");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 前置处理逻辑
System.out.println("Request processing in MyFilter");

// 继续调用下一个过滤器或目标资源
chain.doFilter(request, response);

// 后置处理逻辑(响应处理)
System.out.println("Response processing in MyFilter");
}

@Override
public void destroy() {
// 释放资源(可选)
System.out.println("Destroying MyFilter");
}
}

在这里我们可以获取到请求的内容ServletRequest,响应的内容ServletResponse,获取请求的内容很简单,只要直接操作ServletRequest就行,但获取响应内容,单靠ServletResponse现在这样还是不行的,以下是ServletResponse无法获取响应内容的原因

  • ServletResponse 的工作方式

ServletResponse 是用于写入响应(而不是读取)的接口。它的主要职责是向客户端发送数据,例如 HTML、JSON、文件流等。数据一旦写入到输出流(OutputStreamWriter),会被直接发送到客户端或缓存,默认情况下不保留在服务端。并且写入不可逆,数据写入输出流后,系统假定它已发送,原始数据不会被存储在内存中。因此,无法通过 ServletResponse 直接读取已写入的数据。

  • ServletOutputStream 是一个单向流

ServletResponse 提供的 ServletOutputStreamPrintWriter 都是面向输出的单向流,设计目的是将数据从服务器发送到客户端。单向流的特点是数据写入后无法直接读取。并且没有“回读”机制,一旦数据写入流中,就会流向网络层或缓冲区,ServletOutputStream 本身没有能力缓存或重现这些数据。

  • 数据流的发送路径

HTTP 响应的处理流程大致如下:①服务器端生成响应内容: 通过 ServletResponse 的流接口将数据写入。②数据发送到客户端: 通过网络传输,数据被发送到客户端(如浏览器)。③客户端接收并渲染响应: 客户端解析 HTTP 响应头和主体并进行处理。

在这个流程中,ServletResponse 的作用是数据的“发送者”,它不负责存储或记录已发送的数据。

  • ServletResponse 的设计原则

ServletResponse 遵循了 Java 的 单一职责原则流式处理 设计理念:①单一职责:ServletResponse 的职责是生成和发送 HTTP 响应,而不是存储数据。②流式处理:通过流的方式节约内存,尤其是在处理大数据量的响应时,可以避免内存溢出。

从以上几个方面表明ServletResponse中是无法获取到响应数据的,那么改如何获取到响应的数据呢?虽然无法直接通过 ServletResponse 获取内容,但可以通过以下方式实现:①包装响应对象: 使用 HttpServletResponseWrapper,将数据写入一个临时缓冲区,捕获输出内容。②代理输出流: 自定义 ServletOutputStreamPrintWriter,将所有写入的数据同时记录到缓冲区中,供后续读取。

以下是捕获响应内容的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;

public class CapturingResponseWrapper extends HttpServletResponseWrapper {

private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final PrintWriter writer = new PrintWriter(this.buffer);

public CapturingResponseWrapper(HttpServletResponse response) {
super(response);
}

@Override
public ServletOutputStream getOutputStream() {
return new ServletOutputStreamWrapper(this.buffer);
}

@Override
public PrintWriter getWriter() {
return this.writer;
}

@Override
public void flushBuffer() {
try {
// 确保字符流中的内容写入缓冲区
this.writer.flush();
// 确保缓冲区数据完整
this.buffer.flush();
} catch (Exception e) {
throw new RuntimeException("Error flushing buffer", e);
}
}

public String getContent() {
// 确保所有数据写入缓冲区
this.flushBuffer();
return new String(this.buffer.toByteArray(), StandardCharsets.UTF_8);
}

private static class ServletOutputStreamWrapper extends ServletOutputStream {
private final ByteArrayOutputStream outputStream;

public ServletOutputStreamWrapper(ByteArrayOutputStream outputStream) {
this.outputStream = outputStream;
}

@Override
public void write(int b) {
this.outputStream.write(b);
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setWriteListener(javax.servlet.WriteListener writeListener) {
// No-op
}
}
}

修改之前的MyFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author: Lambda
* @Description: 自定义过滤器
* @Date: 2025/1/5 上午11:37
*/
@Component
public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化逻辑(可选)
System.out.println("Initializing MyFilter");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 前置处理逻辑
System.out.println("Request processing in MyFilter");
CapturingResponseWrapper capturingResponseWrapper = new CapturingResponseWrapper((HttpServletResponse) response);
// 继续调用下一个过滤器或目标资源
chain.doFilter(request, capturingResponseWrapper);
//打印响应的内容
String content = capturingResponseWrapper.getContent();
System.out.println("Response content " + content);
// 后置处理逻辑(响应处理)
System.out.println("Response processing in MyFilter");
//获取完响应的内容后记得把数据重新写入ServletResponse
response.getWriter().write(content);
}

@Override
public void destroy() {
// 释放资源(可选)
System.out.println("Destroying MyFilter");
}
}

可以看到已经获取到了响应的内容

image-20250105122316109

注册过滤器

通过 @Component 注解

将自定义过滤器声明为 Spring 的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import javax.servlet.*;
import java.io.IOException;

/**
* @Author: Lambda
* @Description: 自定义过滤器
* @Date: 2025/1/5 上午11:37
*/
@Component
public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化逻辑(可选)
System.out.println("Initializing MyFilter");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 前置处理逻辑
System.out.println("Request processing in MyFilter");

// 继续调用下一个过滤器或目标资源
chain.doFilter(request, response);

// 后置处理逻辑(响应处理)
System.out.println("Response processing in MyFilter");
}

@Override
public void destroy() {
// 释放资源(可选)
System.out.println("Destroying MyFilter");
}
}

特点:自动注册,不需要额外配置。默认对所有请求生效。

通过 FilterRegistrationBean

如果需要更细粒度的控制(如设置特定 URL 映射或顺序),使用 FilterRegistrationBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

@Bean
public FilterRegistrationBean<MyFilter> myFilterRegistration() {
FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();

// 注册自定义过滤器
registrationBean.setFilter(new MyFilter());

// 设置过滤器路径
registrationBean.addUrlPatterns("/api/*");

// 设置过滤器顺序(值越小,优先级越高)
registrationBean.setOrder(1);

return registrationBean;
}
}

Interceptor(拦截器)

拦截器也是Spring的拦截机制之一,作用范围在 Spring MVC 层,作用于处理器(Controller)的调用。基于 HandlerInterceptor 接口。调用顺序在过滤器之后,切面之前执行。内部的调用顺序按 WebMvcConfigurer 中注册顺序执行。可以拿到的数据:①请求数据HttpServletRequestHttpServletResponse。②处理器信息:目标 Handler(通常是 Controller 方法)。功能:①请求预处理:在请求到达 Controller 之前进行处理。②响应后处理:在 Controller 返回结果后但尚未生成响应时进行处理。③异常处理:捕获 Controller 发生的异常。④会话管理:验证用户会话、检查权限等。

拦截器生命周期

我们主要接触的生命周期只有preHandle,postHandlerafterCompletion

拦截器.drawio

拦截器的使用

实现 HandlerInterceptor 接口,创建一个拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.lambda.projectcore.aspect;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class CustomInterceptor implements HandlerInterceptor {

// 处理器方法调用前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle: 请求被拦截");
// 可以通过返回 false 来阻止请求继续
return true;
}

// 处理器方法调用后执行(但视图未渲染)
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle: 方法执行完成");
}

// 请求处理完成后执行(包括视图渲染后)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion: 请求完成");
}
}

拦截器中获取响应的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@ControllerAdvice
public class InterceptResponse implements ResponseBodyAdvice<Object> {

private static final Integer MAX_LENGTH = 1000;

@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}

@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {

String result;
ObjectMapper objectMapper = new ObjectMapper();
try {
result = objectMapper.writeValueAsString(body);
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON转换失败", e);
}
Integer length = result.length();
result = result.substring(0, length);
HttpServletRequest httpServletRequest = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession httpSession = httpServletRequest.getSession(true);
//放到缓存里,以便于可以在HandlerInterceptor拦截里取出并打印出返回结果
httpSession.setAttribute("body", result);
return body;
}
}

修改CustomInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Component
public class CustomInterceptor implements HandlerInterceptor {

// 处理器方法调用前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle: 请求被拦截");
// 可以通过返回 false 来阻止请求继续
return true;
}

// 处理器方法调用后执行(但视图未渲染)
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle: 方法执行完成");

}

// 请求处理完成后执行(包括视图渲染后)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion: 请求完成");
HttpSession httpSession = request.getSession();
String result = (String) httpSession.getAttribute("body");
System.out.println("afterCompletion: 响应内容:" + result);
}
}

可以看到,获取到了响应的内容

image-20250105133042363

注册拦截器

在 Spring 中,需要将拦截器注册到拦截器链中,可以通过实现 WebMvcConfigurer 接口来完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.lambda.projectcore.aspect;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Autowired
private CustomInterceptor customInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login", "/static/**"); // 排除特定路径
}
}