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 {
//省略非关键代码
}