在项目开发中,常常会同时配置拦截器(Interceptor)和过滤器(Filter),以下就是它们两个主要的区别:
过滤器(Filter)
配置和实现
Filter的实现还是很简单的,可以实现对应的Filter接口,也可以通过@WebFilter注解对Url进行拦截,其中主要是有三个方法:
- init():在容器启动初始化锅炉其的时候被调用,在过滤器的整个生命周期之内进会被调用一次,如果执行失败,过滤器就不会生效
- doFilter():在容器的每一次请求都会触发这个方法,通过FilterChain来调用下一个过滤器
- destroy():当容器销毁过滤器的实例的时候调用,一般用于销毁或关闭资源,在整个生命周期中也是只会被调用一次
@Component public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("Filter 前置"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Filter 处理中"); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { System.out.println("Filter 后置"); } }
拦截器(Interceptor)
配置和实现
拦截器是链式调用的,一个应用中可以存在多个拦截器,一个请求可以触发的多个拦截器,并且每一个拦截器按照声明的顺序依次执行。主要是通过实现HandlerInterceptor接口,其中主要是有三个方法:
- preHandler():在请求之前调用。如果返回false,当前请求结束,后续也不会执行
- postHandler():只有preHandler返回true才会执行,在Controller中的方法调用之后、
DispatcherServlet
返回渲染视图之前被调用 - afterCompletion():只有
preHandle()
方法返回值为true
时才会执行,在整个请求结束之后、DispatcherServlet
渲染了对应的视图之后执行@Component public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Interceptor 前置"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("Interceptor 处理中"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("Interceptor 后置"); } }
过滤器和拦截器区别
过滤器和拦截器的主要区别有以下几点:
- 底层实现不同:
- 过滤器:底层通过函数回调来实现。在自定义过滤器中实现的
doFilter()
方法有一个FilterChain
参数,它是一个回调接口。 - 拦截器:Java的反射机制(动态代理)。
- 过滤器:底层通过函数回调来实现。在自定义过滤器中实现的
- 使用范围:
- 过滤器:依赖
Tomcat
等容器,仅用于web
程序。 - 拦截器:由
Spring
容器管理,可用于多种程序。
- 过滤器:依赖
- 触发时机(重点):
- 过滤器:在请求进入到容器后,进入servlet之前进行预处理,请求结束是在
servlet
处理完以后。 - 拦截器:拦截器是在servlet之后,进入到Controller之前进行预处理,在
Controller
中渲染了对应的视图之后请求结束。
- 过滤器:在请求进入到容器后,进入servlet之前进行预处理,请求结束是在
- 注入Bean的的情况不同:
- 过滤器:在过滤器中注入service是可行的。
- 拦截器:在拦截器中注入service是不可行的,原因是拦截器的加载是在spring容器初始化之前的,如果如果注入会出现错误。解决方案就是手动注入如下
@Configuration public class MyMvcConfig implements WebMvcConfigurer { //让 Spring 容器能够通过这个方法来获取MyInterceptor实例 @Bean public MyInterceptor getMyInterceptor(){ System.out.println("注入了MyInterceptor"); return new MyInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**"); } }
使用registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");来注册拦截器。这里不是直接使用new MyInterceptor(),而是调用getMyInterceptor方法获取实例。这样做的好处是,在获取MyInterceptor实例时,由于getMyInterceptor方法是由 Spring 容器管理的(因为它在@Configuration类中),Spring 会确保在创建MyInterceptor实例时,相关的依赖(如可能需要注入的 Service)已经被正确初始化。
使用场景
一般在项目中,这两个是配合在一起来进行使用的,比如我在项目中实现用户上下文的打通,首先通过过滤器拦截请求,将用户的唯一id存入到请求的body当中,再通过拦截器,从body中获取openid存入到封装的用户上下文对象当中。
//在网关集成过滤器
@Component
@Slf4j
public class LoginFilter implements GlobalFilter {
@Override
@SneakyThrows
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
ServerHttpRequest.Builder mutate = request.mutate();
String url = request.getURI().getPath();
log.info("LoginFilter.filter.url:{}", url);
if (url.equals("/user/doLogin")) {
return chain.filter(exchange);
}
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
log.info("LoginFilter.filter.url:{}", new Gson().toJson(tokenInfo));
String loginId = (String) tokenInfo.getLoginId();
mutate.header("loginId", loginId);
return chain.filter(exchange.mutate().request(mutate.build()).build());
}
}
public class LoginIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String loginId = request.getHeader("loginId");
LoginContextHolder.set("loginId",loginId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
LoginContextHolder.remove();
}
}