拦截器和过滤器的同异

准备环境


我们在项目中同时配置 拦截器 和 过滤器。

1、过滤器 (Filter)过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter接口中定义了三个方法。

  • init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。

  • doFilter() :容器中的每一次请求都会调用该方法, FilterChain用来调用下一个过滤器 Filter。

  • destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter的整个生命周期也只会被调用一次

 @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 后置");
         }
 }

2、拦截器 (Interceptor)拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行。

首先编写一个简单的拦截器处理类,请求的拦截是通过HandlerInterceptor 来实现,看到HandlerInterceptor接口中也定义了三个方法。

preHandle() :这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。

postHandle():只有在preHandle() 方法返回值为true 时才会执行。会在Controller中的方法调用之后,DispatcherServlet返回渲染视图之前被调用。 有意思的是:postHandle()方法被调用的顺序跟 preHandle()是相反的,先声明的拦截器 preHandle()方法先执行,而postHandle()方法反而会后执行。

afterCompletion():只有在 preHandle()方法返回值为true时才会执行。在整个请求结束之后,DispatcherServlet渲染了对应的视图之后执行。

 @Component
 public class MyInterceptor implements HandlerInterceptor {   
    /***
      * 在请求处理之前进行调用(Controller方法调用之前)
      */
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         return HandlerInterceptor.super.preHandle(request, response, handler);
         //如果设置为false时,被请求时,拦截器执行到此处将不会继续操作
         //如果设置为true时,请求将会继续执行后面的操作
     }
 ​
     /***
      * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
      */
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
         HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
     }
 ​
     /***
      * 整个请求结束之后被调用,也就是在DispatchServlet渲染了对应的视图之后执行(主要用于进行资源清理工作)
      */
     @Override
     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
         HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
     }
 }

将自定义好的拦截器处理类进行注册,并通过addPathPatterns、excludePathPatterns等属性设置需要拦截或需要排除的 URL。

 @Configuration
 public class MyMvcConfig implements WebMvcConfigurer {
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
         registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
         }
 }

区别


过滤器 和 拦截器 均体现了`AOP`的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的,接下来一一说明。

1、实现原理不同

过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。

这里重点说下过滤器!

在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。

 public interface FilterChain {
     void doFilter(ServletRequestvar1, ServletResponsevar2) throwsIOException, ServletException;
 }

ApplicationFilterChain里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法。
 public final class ApplicationFilterChain implements FilterChain {
     @Override
     public void doFilter(ServletRequest request, ServletResponse response) {
             ...//省略
             internalDoFilter(request,response);
     }
 ​
     private void internalDoFilter(ServletRequest request, ServletResponse response){
     if (pos < n) {
             //获取第pos个filter    
             ApplicationFilterConfig filterConfig = filters[pos++];        
             Filter filter = filterConfig.getFilter();
             ...
             filter.doFilter(request, response, this);
         }
     }
 }
而每个xxxFilter会先执行自身的 doFilter()过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain的
doFilter()方法,以此循环执行实现函数回调。
 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
 ​
     filterChain.doFilter(servletRequest, servletResponse);
 }

2、使用范围不同

我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。

3、触发时机不同

过滤器 和 拦截器的触发时机也不同,我们看下边这张图。

过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

4、拦截的请求范围不同

在上边我们已经同时配置了过滤器和拦截器,再建一个Controller接收请求测试一下。

 @Controller
 @RequestMapping()
 public class Test {
    @RequestMapping("/test1")
     @ResponseBody
     public String test1(String a) {
         System.out.println("我是controller");
         return null;
         }
 }

项目启动过程中发现,过滤器的init()方法,随着容器的启动进行了初始化。

此时浏览器发送请求,F12 看到居然有两个请求,一个是我们自定义的 Controller 请求,另一个是访问静态图标资源的请求。

看到控制台的打印日志如下:

执行顺序 :Filter 处理中 -> Interceptor 前置 -> 我是controller -> Interceptor 处理中 -> Interceptor 处理后

Filter 处理中

Interceptor 前置

Interceptor 处理中

Interceptor 后置

Filter 处理中

过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。

5、注入Bean情况不同

在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service服务。

下边我们分别在过滤器和拦截器中都注入service,看看有什么不同?

 @Component
 public class TestServiceImpl implements TestService {
    @Override
     public void a() {
         System.out.println("我是方法A");
         }
 }
过滤器中注入service,发起请求测试一下 ,日志正常打印出“我是方法A”。
     @Autowired
     private TestService testService;
 ​
     @Override
     public void doFilter(ServletRequestservletRequest, ServletResponseservletResponse, FilterChainfilterChain) throwsIOException, ServletException {
 ​
         System.out.println("Filter 处理中");
         testService.a();
         filterChain.doFilter(servletRequest, servletResponse);
     }

Filter 处理中

我是方法A

Interceptor 前置

我是controller

Interceptor 处理中

Interceptor 后置

在拦截器中注入service,发起请求测试一下 ,竟然TM的报错了,debug跟一下发现注入的service怎么是Null啊?

这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。

拦截器:老子今天要进洞房;

Spring:兄弟别闹,你媳妇我还没生出来呢!

解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。注意:在registry.addInterceptor()注册的是getMyInterceptor() 实例。

 @Configuration
 public class MyMvcConfig implementsWebMvcConfigurer {
 ​
     @Bean
     public MyInterceptorget MyInterceptor(){
         System.out.println("注入了MyInterceptor");
         returnnewMyInterceptor();
     }
     
     @Override
     public void addInterceptors(InterceptorRegistryregistry) {
 ​
         registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
     }
 }

6、控制执行顺序不同

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。

 @Order(Ordered.HIGHEST_PRECEDENCE)
 @Component
 public class MyFilter2 implementsFilter {
//拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。
 @Override
     public void addInterceptors(InterceptorRegistryregistry) {
         registry.addInterceptor(newMyInterceptor2()).addPathPatterns("/**").order(2);
         registry.addInterceptor(newMyInterceptor1()).addPathPatterns("/**").order(1);
         registry.addInterceptor(newMyInterceptor()).addPathPatterns("/**").order(3);
     }

看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。

postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。

Interceptor1 前置

Interceptor2 前置

Interceptor 前置

我是controller

Interceptor 处理中

Interceptor2 处理中

Interceptor1 处理中

Interceptor 后置

Interceptor2 处理后

Interceptor1 处理后

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值