Spring Web 过滤器使用常见错误

1.@WebFilter 过滤器无法被自动注入

为了统计接口耗时,可以实现一个过滤器如下

@WebFilter
@Slf4j
public class TimeCostFilter implements Filter {
    public TimeCostFilter(){
        System.out.println("construct");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("开始计算接口耗时");
        long start = System.currentTimeMillis();
        chain.doFilter(request, response);
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("执行时间(ms):" + time);
    }
}

这个过滤器标记了 @WebFilter。所以在启动程序中,我们需要加上扫描注解(即 @ServletComponentScan)让其生效

假设有一天,我们可能需要把 TimeCostFilter 记录的统计数据输出到专业的度量系统(ElasticeSearch/InfluxDB 等)里面去,我们可能会添加这样一个 Service 类


@Service
public class MetricsService {

    @Autowired
    public TimeCostFilter timeCostFilter;
    //省略其他非关键代码

}

程序将无法启动!

本质上,过滤器被 @WebFilter 修饰后,TimeCostFilter 只会被包装为 FilterRegistrationBean,而 TimeCostFilter 自身,只会作为一个 InnerBean 被实例化,这意味着 TimeCostFilter 实例并不会作为 Bean 注册到 Spring 容器
@WebFilter不属于spring,Spring Boot 使用了FilterRegistrationBean 来包装 @WebFilter 标记的实例
最终 TimeCostFilter 实例是一种 InnerBean,无法被依赖注入

修正:


@Controller
@Slf4j
public class StudentController {
    @Autowired
    @Qualifier("com.spring.puzzle.filter.TimeCostFilter")FilterRegistrationBean timeCostFilter;
 
}

// 也可以直接使用@Component + Filter方式实现过滤器

2.Filter 中不小心多次执行 doFilter()

不管怎么调用,不能多次调用 FilterChain#doFilter()

3.@WebFilter 过滤器使用 @Order 无效


@WebFilter
@Slf4j
@Order(2)
public class AuthFilter implements Filter {
    @SneakyThrows
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        if(isPassAuth()){
            System.out.println("通过授权");
            chain.doFilter(request, response);
        }else{
            System.out.println("未通过授权");
            ((HttpServletResponse)response).sendError(401);
        }
    }
    private boolean isPassAuth() throws InterruptedException {
        System.out.println("执行检查权限");
        Thread.sleep(1000);
        return true;
    }
}




@WebFilter
@Slf4j
@Order(1)
public class TimeCostFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("#开始计算接口耗时");
        long start = System.currentTimeMillis();
        chain.doFilter(request, response);
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("#执行时间(ms):" + time);
    }
}

以上@Order标注的代码并不符合预期
当一个请求来临时,会执行到 StandardWrapperValve 的 invoke(),这个方法会创建 ApplicationFilterChain,并通过 ApplicationFilterChain#doFilter() 触发过滤器执行,并最终执行到内部私有方法 internalDoFilter()

RegistrationBean 中 order 属性的值最终可以决定过滤器的执行顺序。但是可惜的是:当使用 @WebFilter 时,构建的 FilterRegistrationBean 并没有依据 @Order 的值去设置 order 属性,所以 @Order 失效了

解决办法:


@Configuration
public class FilterConfiguration {
    @Bean
    public FilterRegistrationBean authFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new AuthFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(2);
        return registration;
    }

    @Bean
    public FilterRegistrationBean timeCostFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new TimeCostFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(1);
        return registration;
    }
}

4.过滤器被多次执行

两个过滤器中增加 @Component,从而让 @Order 生效,但是实际程序过滤器执行了两次

  • 理论上 Spring 会根据当前类再次包装一个新的过滤器,因而 doFIlter() 被执行两次。
  • 任何通过 @Component 修饰的的类,都可以自动注册到 Spring,且能被 Spring 直接实例化

解决办法:

//@WebFilter
@Slf4j
@Order(1)
@Component
public class TimeCostFilter implements Filter {
   //省略非关键代码
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值