You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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请求的干扰,同时能直接获取到目标方法的注解信息,实现更简洁。

实现步骤:

  1. 定义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");
    }
}
  1. 注册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逻辑。

实现步骤:

  1. 改造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");
        }
    }
}
  1. 注册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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 09:15:40