【硬核问题】@RequestHeader不能捕捉使用FilterRegistrationBean配置的过滤器Filter

背景:修改一个http请求的Header。希望把Header中的X-Biz-App根据properties中的配置进行截断一部分(截断第二个’|'往后的字符)。
开发:修改Header第一时间想到过滤器。但是过滤器不能访问通过@value加载配置参数,手动加载代价又太高不合适。所以采用FilterRegistrationBean动态的注册Filter。并在自定义的Filter类中通过重写getHeader方法进行实现。
代码如下

  1. 注册Filter配置类TeslaFilterConfig
@Configuration
public class TeslaFilterConfig {
    @Value("${filter.biz-app:}")
    private String bizAppEnv;

    @Bean
    public FilterRegistrationBean regBizAppFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new BizAppFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("bizAppEnv", bizAppEnv);
        filterRegistrationBean.setOrder(-1);
        filterRegistrationBean.setEnabled(true);
        return filterRegistrationBean;
    }
}
  1. 自定义Filter类BizAppFilter:
public class BizAppFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse, FilterChain filterChain)
            throws ServletException, IOException {
        String bizAppEnv = getFilterConfig().getInitParameter("bizAppEnv");
        HttpServletRequestWrapper httpServletRequestWrapper = new HttpServletRequestWrapper(httpServletRequest) {
            @Override
            public String getHeader(String name) {
                if (StringUtils.isNotEmpty(name) && name.equalsIgnoreCase(HttpHeaderNames.X_BIZ_APP)) {
                    if (StringUtils.isNotEmpty(bizAppEnv) && bizAppEnv.equalsIgnoreCase("env-group")) {
                        String bizApp = super.getHeader(HttpHeaderNames.X_BIZ_APP);
                        if(StringUtils.isNotEmpty(bizApp)){
                            StringBuilder resBizApp = new StringBuilder();
                            int flag = 0;
                            for (Character c : bizApp.toCharArray()) {
                                if (c == '|' && flag == 1) {
                                    return resBizApp.toString();
                                } else if (c == '|') {
                                    flag += 1;
                                }
                                resBizApp.append(c);
                            }
                            return resBizApp.toString();
                        }
                    }
                    return super.getHeader(name);
                } else {
                    return super.getHeader(name);
                }
            }
        };
        filterChain.doFilter(httpServletRequestWrapper, httpServletResponse);
    }
}
  1. 定义Controller:
    @RequestMapping(value = "/filter1", method = {RequestMethod.GET})
    @ResponseBody
    public TeslaBaseResult getAdminApplications(
        @RequestHeader(value = "X-Biz-App") String bizApp
    ) {
        // return buildSucceedResult(applicationService.getAdminApplications(userEmpid));
        return buildSucceedResult(bizApp);
    }

    @RequestMapping(value = "/filter2")
    @ResponseBody
    public TeslaBaseResult testFilter(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse){
        String res = httpServletRequest.getHeader("X-Biz-App");
        return buildSucceedResult(res);
    }

测试
自定义请求头:X-Biz-App: platform|group|test|111分别发给filter1和filter2接口,所得:
filter1结果:

{
  "code": 200,
  "message": "OK",
  "data": "platform|group|test|111"
}

filter2结果:

{
  "code": 200,
  "message": "OK",
  "data": "platform|group"
}

可见,通过@RequestHeader注解获取的值并没有被过滤器截断,而通过httpServletRequest的getHeader方法却可以正确获取
分析:Filter不是应该在请求进入spring容器之前生效吗?注解获取header参数的方式又是什么样的,为什么会绕开Filter?

试着把Filter的order属性设置得更小。

设为-1等都试过了。感觉问题不在于filter注册先后。

可能有关系,你设置为-9999试试看。。
我记得spring有个Filter会把req和rep绑定到线程。后面执行参数绑定啥的会用到线程上下文的这个req/rep。。如果那个 Filter先执行的话,可能有影响。

-2147483636最小值试过也不行

debug跟一下吧 :joy:

HttpServletRequest 获取 Header 还有一个方法

Enumeration<String> getHeaders(String name)

你把这个方法也给覆写了试试看?

是的,@RequestHeader注解赋值是调用RequestFacade类的getHeaders方法,我这里只覆盖了getHeader了。