【框架篇】过滤器和拦截器的区别以及使用场景

在项目开发中,常常会同时配置拦截器(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中渲染了对应的视图之后请求结束。

  • 注入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();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lose_rose777

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值