Spring Boot拦截HTTP请求与设置请求头实战指南

Spring Boot拦截HTTP请求与设置请求头实战指南 1. Spring Boot拦截HTTP请求设置请求头的核心场景在Web开发中经常需要统一处理HTTP请求和响应。比如在微服务架构中我们可能需要在所有请求中添加认证头如JWT Token或者记录请求日志、修改响应内容。Spring Boot提供了多种方式来实现这种拦截需求其中最常见的是通过过滤器Filter和拦截器Interceptor。关键区别Filter是Servlet规范的一部分工作在更底层Interceptor是Spring MVC的组件可以访问Spring上下文。我最近在一个电商项目中就遇到了这样的需求需要在所有API请求中添加一个X-Request-ID头用于全链路追踪。经过对比几种方案最终选择了过滤器的方式实现。下面分享具体实现过程和踩过的坑。2. 实现HTTP请求拦截的三种核心方式2.1 使用Servlet Filter这是最基础的方式适合简单的请求/响应修改。创建一个实现javax.servlet.Filter接口的类public class CustomHeaderFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; HttpServletResponse httpResponse (HttpServletResponse) response; // 添加自定义请求头 httpResponse.setHeader(X-Custom-Header, value); // 继续过滤器链 chain.doFilter(httpRequest, httpResponse); } }注册过滤器有两种方式使用Component注解自动注册通过FilterRegistrationBean手动注册推荐可以控制顺序Configuration public class FilterConfig { Bean public FilterRegistrationBeanCustomHeaderFilter filterRegistrationBean() { FilterRegistrationBeanCustomHeaderFilter registration new FilterRegistrationBean(); registration.setFilter(new CustomHeaderFilter()); registration.addUrlPatterns(/*); registration.setOrder(1); // 设置过滤器顺序 return registration; } }2.2 使用Spring InterceptorInterceptor可以访问Spring上下文更适合需要依赖注入的场景。实现步骤创建拦截器类实现HandlerInterceptorpublic class CustomHeaderInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { response.setHeader(X-Interceptor-Header, interceptor-value); return true; } }注册拦截器Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new CustomHeaderInterceptor()) .addPathPatterns(/**); } }2.3 使用ControllerAdvice ResponseBodyAdvice对于只需要修改响应体的场景可以使用这个组合ControllerAdvice public class CustomHeaderAdvice implements ResponseBodyAdviceObject { Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return true; } Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { response.getHeaders().add(X-Advice-Header, advice-value); return body; } }3. 实战实现全链路请求ID追踪下面通过一个完整示例演示如何在微服务中实现请求追踪。我们需要如果请求没有X-Request-ID头生成一个UUID将该ID添加到响应头在日志中记录这个ID3.1 创建RequestIdFilterpublic class RequestIdFilter implements Filter { private static final String REQUEST_ID_HEADER X-Request-ID; private static final Logger logger LoggerFactory.getLogger(RequestIdFilter.class); Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; HttpServletResponse httpResponse (HttpServletResponse) response; // 获取或生成请求ID String requestId httpRequest.getHeader(REQUEST_ID_HEADER); if (requestId null || requestId.isEmpty()) { requestId UUID.randomUUID().toString(); } // 添加到响应头 httpResponse.setHeader(REQUEST_ID_HEADER, requestId); // 使用MDC记录请求ID便于日志追踪 MDC.put(requestId, requestId); try { logger.info(Incoming request: {} {}, httpRequest.getMethod(), httpRequest.getRequestURI()); chain.doFilter(httpRequest, httpResponse); } finally { MDC.remove(requestId); } } }3.2 配置日志模式在application.properties中添加logging.pattern.console%d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{requestId}] %-5level %logger{36} - %msg%n3.3 注册过滤器Configuration public class WebConfig { Bean public FilterRegistrationBeanRequestIdFilter requestIdFilter() { FilterRegistrationBeanRequestIdFilter registration new FilterRegistrationBean(); registration.setFilter(new RequestIdFilter()); registration.addUrlPatterns(/*); registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最高优先级 return registration; } }4. 高级应用场景与优化4.1 性能优化避免频繁创建对象在过滤器中使用ThreadLocal缓存常用对象public class PerformanceFilter implements Filter { private static final ThreadLocalSimpleDateFormat dateFormatHolder ThreadLocal.withInitial(() - new SimpleDateFormat(yyyy-MM-dd HH:mm:ss)); Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 使用缓存的对象 String formattedDate dateFormatHolder.get().format(new Date()); response.setHeader(X-Process-Time, formattedDate); chain.doFilter(request, response); } }4.2 安全头设置常见的安全头设置示例public class SecurityHeadersFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse (HttpServletResponse) response; // 防止点击劫持 httpResponse.setHeader(X-Frame-Options, DENY); // XSS保护 httpResponse.setHeader(X-XSS-Protection, 1; modeblock); // 禁止MIME类型嗅探 httpResponse.setHeader(X-Content-Type-Options, nosniff); // CSP策略 httpResponse.setHeader(Content-Security-Policy, default-src self); chain.doFilter(request, response); } }4.3 动态修改请求头有时我们需要在拦截器中修改请求头。由于HttpServletRequest的头部是只读的我们需要使用包装类public class HeaderModificationFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new HttpServletRequestWrapper((HttpServletRequest) request) { Override public String getHeader(String name) { if (User-Agent.equalsIgnoreCase(name)) { return Modified-Agent; } return super.getHeader(name); } Override public EnumerationString getHeaders(String name) { if (User-Agent.equalsIgnoreCase(name)) { return Collections.enumeration(Arrays.asList(Modified-Agent)); } return super.getHeaders(name); } }, response); } }5. 常见问题与解决方案5.1 过滤器不生效的可能原因顺序问题过滤器可能被其他过滤器提前终止解决方案调整Order或setOrder()值URL模式不匹配确保addUrlPatterns()包含你的路径缺少组件扫描确保配置类在组件扫描路径下5.2 拦截器与过滤器的执行顺序典型请求处理流程客户端请求 - Servlet Filter Chain - DispatcherServlet - Interceptor preHandle - Controller - Interceptor postHandle - Interceptor afterCompletion - Servlet Filter Chain - 客户端响应5.3 性能监控实现示例public class MetricsFilter implements Filter { private static final String START_TIME_ATTR startTime; Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long startTime System.currentTimeMillis(); request.setAttribute(START_TIME_ATTR, startTime); chain.doFilter(request, response); long duration System.currentTimeMillis() - startTime; response.setHeader(X-Processing-Time, duration ms); } }5.4 跨域处理的最佳实践虽然可以在过滤器中处理CORS但推荐使用Spring的专用机制Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(https://trusted.com) .allowedMethods(GET, POST) .allowedHeaders(*) .exposedHeaders(X-Custom-Header) .maxAge(3600); } }6. 测试策略6.1 单元测试过滤器public class CustomHeaderFilterTest { Test public void testHeaderAdded() throws Exception { MockHttpServletRequest request new MockHttpServletRequest(); MockHttpServletResponse response new MockHttpServletResponse(); MockFilterChain chain new MockFilterChain(); new CustomHeaderFilter().doFilter(request, response, chain); assertEquals(value, response.getHeader(X-Custom-Header)); } }6.2 集成测试SpringBootTest AutoConfigureMockMvc public class FilterIntegrationTest { Autowired private MockMvc mockMvc; Test public void testRequestIdHeader() throws Exception { mockMvc.perform(get(/api/test)) .andExpect(header().exists(X-Request-ID)); } }6.3 使用Postman测试发送请求并检查响应头测试不同场景不带请求头的请求带自定义请求头的请求测试安全头是否生效7. 性能考量与最佳实践过滤器链长度每个过滤器都会增加处理时间保持链尽可能短避免阻塞操作不要在过滤器中执行长时间阻塞操作线程安全确保过滤器是线程安全的资源清理在finally块中清理资源如MDC缓存策略对于频繁使用的值考虑使用缓存在最近的一个性能测试中我们发现每增加一个简单的过滤器平均响应时间增加约0.3ms在4核8G的测试环境。当过滤器数量超过10个时性能下降变得明显。因此建议合并相关功能的过滤器将不必要对所有请求生效的过滤器改为只匹配特定路径对于性能关键路径考虑使用更高效的实现方式8. Spring Boot 3.x的新特性在Spring Boot 3.x中过滤器注册有了更简洁的方式Configuration public class FilterConfig { Bean public ServletRegistrationBeanDispatcherServlet dispatcherServletRegistration() { return new ServletRegistrationBean(new DispatcherServlet(), /); } Bean public FilterRegistrationBeanCustomHeaderFilter customHeaderFilter() { return new FilterRegistrationBean(new CustomHeaderFilter()); } }另外Spring Boot 3.x对Servlet API的要求升级到了5.0需要注意兼容性问题。9. 实际项目经验分享在电商项目中实现全链路追踪时我们遇到了几个关键问题异步请求的请求ID传递解决方案使用TaskDecorator包装异步任务Configuration public class AsyncConfig implements AsyncConfigurer { Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setTaskDecorator(new MDCTaskDecorator()); // 其他配置... return executor; } }网关层与微服务的ID传递在网关层生成请求ID通过Feign拦截器传递给下游服务public class FeignRequestInterceptor implements RequestInterceptor { Override public void apply(RequestTemplate template) { String requestId MDC.get(requestId); if (requestId ! null) { template.header(X-Request-ID, requestId); } } }日志系统的集成配置Logback/SLF4J使用MDC中的requestId在ELK等日志系统中建立基于requestId的关联10. 扩展思考更现代的替代方案虽然过滤器和拦截器仍然有效但在新项目中也可以考虑Spring Cloud Gateway过滤器适用于微服务架构的网关层Servlet 3.1的异步处理对于长时间运行的请求Reactive StackWebFlux提供的WebFilter机制Micrometer追踪与分布式追踪系统集成对于全新的Spring Boot 3.x项目WebFlux提供了一种更现代的替代方案Component public class CustomWebFilter implements WebFilter { Override public MonoVoid filter(ServerWebExchange exchange, WebFilterChain chain) { exchange.getResponse().getHeaders().add(X-Custom-Header, value); return chain.filter(exchange); } }这种响应式的方式在处理高并发请求时通常有更好的性能表现。