Spring请求上下文实现方案 | AI生成和翻译
Spring框架中微型”请求上下文”的绝佳应用场景。根据技术栈可选择以下几种简洁方案:
1) 请求作用域Bean(简单安全)
将测试用户ID存储在请求作用域的Bean中,由TestUserFilter设置,任何组件均可注入读取。
// 在单个HTTP请求生命周期内保持值
@Component
@RequestScope
public class TestUserContext {
private String userId;
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
}
@Component
public class TestUserFilter extends OncePerRequestFilter {
private final TestUserContext ctx;
public TestUserFilter(TestUserContext ctx) { this.ctx = ctx; }
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws ServletException, IOException {
String id = req.getHeader("X-Test-User-Id");
if (id == null) id = req.getParameter("testUserId");
if (id != null && !id.isBlank()) ctx.setUserId(id);
chain.doFilter(req, res); // 请求结束后请求作用域Bean自动清理
}
}
任意位置使用:
@Service
public class SomeService {
private final TestUserContext ctx;
public SomeService(TestUserContext ctx) { this.ctx = ctx; }
public void doWork() {
String testUserId = ctx.getUserId(); // 未提供时返回null
}
}
优势: 无需手动清理;同一请求内的控制器/服务/DAO层均可访问。
2) Spring Security上下文(若已使用Spring Security推荐此方案)
通过过滤器设置Authentication,所有组件可通过SecurityContextHolder或@AuthenticationPrincipal读取。
@Component
public class TestUserFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String id = req.getHeader("X-Test-User-Id");
if (id == null) id = req.getParameter("testUserId");
if (id != null && !id.isBlank()) {
var auth = new UsernamePasswordAuthenticationToken(
id, "N/A", List.of(new SimpleGrantedAuthority("ROLE_TEST")));
SecurityContextHolder.getContext().setAuthentication(auth);
}
try {
chain.doFilter(req, res);
} finally {
SecurityContextHolder.clearContext();
}
}
}
任意位置使用:
String testUserId = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
或在控制器中注入Principal/Authentication。
优势: 完美兼容方法级安全、审计、日志等功能。
3) ThreadLocal持有器(无Web/Security依赖时适用;务必记得清理)
未使用请求作用域或Security时的方案:
public final class TestUserHolder {
private static final ThreadLocal<String> USER_ID = new InheritableThreadLocal<>();
public static void set(String id) { USER_ID.set(id); }
public static String get() { return USER_ID.get(); }
public static void clear() { USER_ID.remove(); }
}
@Component
public class TestUserFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String id = req.getHeader("X-Test-User-Id");
if (id == null) id = req.getParameter("testUserId");
if (id != null && !id.isBlank()) TestUserHolder.set(id);
try {
chain.doFilter(req, res);
} finally {
TestUserHolder.clear(); // 对线程池环境至关重要
}
}
}
若需在异步任务中传递值,可通过TaskDecorator复制ThreadLocal。
实用技巧
- 通过
X-Test-User-Id请求头(便于Postman/MockMvc传递)或查询参数传递测试ID - MockMvc单元测试使用:
.header("X-Test-User-Id", "u123") - 需要简单”值容器”时优先选择方案1
- 已集成Spring Security或需要授权/审计时选择方案2
- 仅当不需要Spring作用域/安全功能时使用方案3,务必注意清理避免内存泄漏