如何让Spring Boot Filter仅作用于@GetMapping映射的URL
问题描述
我们维护多个基于Spring Boot REST的Web服务,通过自定义Filter实现两个核心逻辑:验证请求是否包含必要HTTP头、将头信息存入日志MDC。当前遇到的问题是:
- 要求该Filter仅对标注了
@GetMapping注解的URL生效,比如请求不存在的URL(返回404)时不触发Filter逻辑 - 由于Filter位于公共工具库,各服务URL映射模式不固定,无法通过指定固定URL模式实现过滤
- 当前基于
FilterRegistrationBean的实现会导致404请求也触发Filter,不符合需求
可行解决方案
方案1:改用HandlerInterceptor(推荐)
Spring MVC的HandlerInterceptor运行在Controller层面,只有当请求匹配到具体的Controller方法时才会触发,天然避免了404请求的干扰,同时能直接获取到目标方法的注解信息,实现更简洁。
实现步骤:
- 定义Interceptor类,在
preHandle方法中判断目标方法是否标注@GetMapping:
@Component public class MetadataInterceptor implements HandlerInterceptor { private final MetadataContainer metadataContainer; public MetadataInterceptor(MetadataContainer metadataContainer) { this.metadataContainer = metadataContainer; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 仅处理Controller方法请求 if (handler instanceof HandlerMethod handlerMethod) { // 检查方法或类上是否存在@GetMapping注解 boolean isGetMappingRequest = handlerMethod.hasMethodAnnotation(GetMapping.class) || handlerMethod.getBeanType().isAnnotationPresent(GetMapping.class); if (isGetMappingRequest) { // 执行请求头验证逻辑 String requiredHeader = request.getHeader("X-Required-Header"); if (requiredHeader == null) { // 验证不通过时可直接返回400或其他状态码 response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Required header missing"); return false; } // 将头信息存入MDC MDC.put("request-metadata", requiredHeader); } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 清理MDC,避免内存泄漏 MDC.remove("request-metadata"); } }
- 注册Interceptor并配置排除路径:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { private final MetadataInterceptor metadataInterceptor; private final String[] pathExclusions; public WebMvcConfig(MetadataInterceptor metadataInterceptor, @Value("${path-exclusions: /swagger/**, /actuator/**, *.yaml, /api-docs/**, *.ico}") String[] pathExclusions) { this.metadataInterceptor = metadataInterceptor; this.pathExclusions = pathExclusions; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(metadataInterceptor) .addPathPatterns("/**") // 拦截所有请求,内部通过注解判断过滤 .excludePathPatterns(pathExclusions); // 排除无需处理的路径 } }
方案2:在Filter中通过HandlerMapping判断请求是否匹配@GetMapping
如果必须保留Filter的实现方式,可以通过Spring的RequestMappingHandlerMapping提前判断当前请求是否对应标注了@GetMapping的Controller方法,不匹配则跳过Filter逻辑。
实现步骤:
- 改造Filter类,注入
ApplicationContext获取RequestMappingHandlerMapping:
public class MetadataFilter implements Filter { private final RequestMappingHandlerMapping requestMappingHandlerMapping; private final MetadataContainer metadataContainer; public MetadataFilter(ApplicationContext context, MetadataContainer metadataContainer) { this.requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); this.metadataContainer = metadataContainer; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; try { // 查找当前请求对应的处理器链 HandlerExecutionChain handlerChain = requestMappingHandlerMapping.getHandler(httpRequest); if (handlerChain != null) { Object handler = handlerChain.getHandler(); // 判断是否为Controller方法,且标注了@GetMapping if (handler instanceof HandlerMethod handlerMethod) { boolean isGetMappingRequest = handlerMethod.hasMethodAnnotation(GetMapping.class) || handlerMethod.getBeanType().isAnnotationPresent(GetMapping.class); if (isGetMappingRequest) { // 执行请求头验证和MDC存入逻辑 String requiredHeader = httpRequest.getHeader("X-Required-Header"); if (requiredHeader != null) { MDC.put("request-metadata", requiredHeader); } } } } // 继续执行请求链,无论是否匹配都不拦截请求 chain.doFilter(request, response); } finally { // 清理MDC MDC.remove("request-metadata"); } } }
- 注册Filter时配置路径规则:
@Bean public FilterRegistrationBean<MetadataFilter> metadataFilter(ApplicationContext context, MetadataContainer metadataContainer, @Value("${path-exclusions: /swagger/**, /actuator/**, *.yaml, /api-docs/**, *.ico}") String[] pathExclusions) { FilterRegistrationBean<MetadataFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new MetadataFilter(context, metadataContainer)); registrationBean.addUrlPatterns("/*"); // 匹配所有请求,内部通过注解判断过滤 // 添加排除路径 for (String exclusion : pathExclusions) { registrationBean.addUrlPatterns("!" + exclusion); } return registrationBean; }
方案对比
- 方案1(Interceptor):更贴合Spring MVC生态,代码简洁,天然避开404请求,性能更优,优先推荐。
- 方案2(Filter+HandlerMapping):保留Filter原有结构,适合无法切换到Interceptor的场景,但需要额外执行HandlerMapping查找,性能略逊于Interceptor。
内容的提问来源于stack exchange,提问作者Llaurick
相关产品推荐
相关产品推荐

