Java * Interceptor和过滤器Filte的执行顺序和区别

作者:熊與猫v 时间:2022-06-01 20:37:11 

1、实现原理不同

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

1、 * 是基于java的反射机制的,而过滤器是基于函数回调

2、过滤器依赖与servlet容器,而 * 不依赖与servlet容器

3、 * 只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用

4、 * 可以访问action上下文、值栈里的对象,而过滤器不能

5、在action的生命周期中, * 可以多次被调用,而过滤器只能在容器初始化时被调用一次

这里重点说下过滤器!

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


public interface FilterChain {
   void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, 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程序中使用。

Java * Interceptor和过滤器Filte的执行顺序和区别

而 * (Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。

Java * Interceptor和过滤器Filte的执行顺序和区别

3、触发时机不同

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

Java * Interceptor和过滤器Filte的执行顺序和区别

过滤器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()方法,随着容器的启动进行了初始化。

Java * Interceptor和过滤器Filte的执行顺序和区别

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

Java * Interceptor和过滤器Filte的执行顺序和区别

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

执行顺序 :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(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

System.out.println("Filter 处理中");
       testService.a();
       filterChain.doFilter(servletRequest, servletResponse);
   }
Filter 处理中
我是方法A
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor 后置

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

Java * Interceptor和过滤器Filte的执行顺序和区别

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

* :老子今天要进洞房;
Spring:兄弟别闹,你媳妇我还没生出来呢!

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


@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

@Bean
   public MyInterceptor getMyInterceptor(){
       System.out.println("注入了MyInterceptor");
       return new MyInterceptor();
   }

@Override
   public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
   }
}

6、控制执行顺序不同

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

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


@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {

* 默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。


@Override
   public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
       registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
       registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
   }

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

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

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后

那为什么会这样呢? 得到答案就只能看源码了,我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而 * postHandle()、preHandle()方法便是在其中调用的。


protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

try {
        ...........
           try {

// 获取可以执行当前Handler的适配器
               HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
               String method = request.getMethod();
               boolean isGet = "GET".equals(method);
               if (isGet || "HEAD".equals(method)) {
                   long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                   if (logger.isDebugEnabled()) {
                       logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                   }
                   if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                       return;
                   }
               }
               // 注意: 执行Interceptor中PreHandle()方法
               if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                   return;
               }

// 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
               mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
                   return;
               }
               applyDefaultViewName(processedRequest, mv);

// 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
               mappedHandler.applyPostHandle(processedRequest, response, mv);
           }
       }
       ...........
   }

看看两个方法applyPreHandle()、applyPostHandle()具体是如何被调用的,就明白为什么postHandle()、preHandle() 执行顺序是相反的了。


boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
       HandlerInterceptor[] interceptors = this.getInterceptors();
       if(!ObjectUtils.isEmpty(interceptors)) {
           for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
               HandlerInterceptor interceptor = interceptors[i];
               if(!interceptor.preHandle(request, response, this.handler)) {
                   this.triggerAfterCompletion(request, response, (Exception)null);
                   return false;
               }
           }
       }

return true;
   }
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
       HandlerInterceptor[] interceptors = this.getInterceptors();
       if(!ObjectUtils.isEmpty(interceptors)) {
           for(int i = interceptors.length - 1; i >= 0; --i) {
               HandlerInterceptor interceptor = interceptors[i];
               interceptor.postHandle(request, response, this.handler, mv);
           }
       }
   }

发现两个方法中在调用 * 数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()、preHandle() 方法执行的顺序相反。

来源:https://blog.csdn.net/qq_36448800/article/details/119196716

标签:Java, , ,过滤器,执行顺序
0
投稿

猜你喜欢

  • spring boot actuator监控超详细教程

    2021-06-27 01:29:36
  • springboot 整合 seata的配置过程

    2023-01-13 01:28:33
  • java中Scanner类的简单用法分享

    2023-04-16 04:24:50
  • Android编程动态按钮实现方法

    2021-12-31 18:05:17
  • 利用OPENCV为android开发畸变校正的JNI库方法

    2021-10-06 17:33:27
  • SpringCloud启动失败问题汇总

    2021-07-28 02:45:51
  • Mybatis一对多关联关系映射实现过程解析

    2021-07-13 06:22:59
  • Kotlin协程Context应用使用示例详解

    2023-07-10 08:25:16
  • 详解如何让Spring MVC显示自定义的404 Not Found页面

    2023-12-12 15:48:47
  • 如何用Dos命令运行Java版HelloWorld你知道吗

    2022-07-24 22:32:26
  • C#打包应用程序,与.NETFramework介绍

    2022-03-29 15:29:20
  • java读写二进制文件的解决方法

    2022-08-03 14:45:55
  • Spring Boot示例分析讲解自动化装配机制核心注解

    2022-07-26 15:56:14
  • Android应用中使用SharedPreferences类存储数据的方法

    2022-06-11 20:27:08
  • zookeeper概述图文详解

    2021-12-07 15:46:03
  • Android开发之TabHost选项卡及相关疑难解决方法

    2022-01-10 06:30:57
  • Mybatis 动态SQL的几种实现方法

    2023-11-10 12:15:15
  • idea中的Maven导包失败问题解决方案汇总

    2023-07-12 12:34:22
  • Android实现一个丝滑的自动轮播控件实例代码

    2022-01-24 03:08:19
  • Java深入浅出数组的定义与使用上篇

    2022-03-10 22:32:58
  • asp之家 软件编程 m.aspxhome.com