(一)回调函数与lambda表达式
在讲过滤器的原理之前,我们先来了解一下什么是回调函数。我们在平时开发中,经常会遇到模块之间的互相调用,调用的方式主要分为以下三种:
1. 同步调用
同步调用是最基本并且最简单的一种调用方式,同时也是最常用的一种方式(也许也是许多人唯一了解的调用方式)。类A的方法a()调用类B的方法b(),一直等待b()方法直到执行完毕,a()方法继续往下走。这种调用方式适用于方法b()执行时间不长的情况,因为b()方法执行时间一长或者直接阻塞的话,a()方法的余下代码是无法执行下去的,这样会造成整个流程的阻塞。
2. 异步调用
异步调用是为了解决同步调用可能出现阻塞,导致整个流程卡住而产生的一种调用方式。类A的方法方法a()通过新起线程的方式调用类B的方法b(),代码接着直接往下执行,这样无论方法b()执行时间多久,都不会阻塞住方法a()的执行。但是这种方式,由于方法a()不等待方法b()执行完成,在方法a()需要方法b()执行结果的情况下(视具体业务而定,有些业务比如启异步线程发个微信通知、刷新一个缓存这种就没必要),必须通过一定的方式对方法b()的执行结果进行监听。在Java中,可以使用Future+Callable的方式做到这一点。
3. 回调
回调的思想是,类A的a()方法调用类B的b()方法,类B的b()方法执行完毕主动调用类A的callback()方法,这样一种调用方式可以看成是一种双向的调用方式。
在C语言中,函数名可以当做函数指针传递给形参从而实现回调。但是在Java中,是不允许将一个方法作为参数传递给另一个函数,也不允许将一个方法作为值,返回给调用者,这就是所谓的不支持闭包。那么Java中如果想要实现回调函数的功能,我们该如何操作呢?
Java中回调函数有好几种写法,可以用反射、抽象类,还可以用接口。这里我们就选用Java的新特性——lambda表达式来写,因为这种写法更简便。先介绍下代码示例的背景,在main函数中,我们异步发送一个请求,并且指定处理响应的回调函数,接着main函数去运行下面的代码,而当响应到达后,执行回调函数。
class Request{ public void send(Runnable runnable) throws Exception { // 模拟阻塞 Thread.sleep(3000); System.out.println("[Request]:收到响应"); runnable.run(); } } public class Test { public static void main(String[] args) throws InterruptedException { Request request = new Request(); System.out.println("[Main]:新建线程去异步发请求"); new Thread(() -> { try { request.send(()-> System.out.println("[CallBack]:处理响应")); } catch (Exception e) { e.printStackTrace(); } }).start(); System.out.println("[Main]:发送完成,执行下面代码"); Thread.sleep(100000); } }
这是比较典型的异步回调(若需求为同步回调,只需要删去新建线程的过程即可),我们分析一下这段代码。首先,Request模块模拟为一个会阻塞的发送请求的操作,阻塞时间为三秒。我们的需求是在main方法中可以指定处理响应的操作(即Request模块收到响应后执行处理响应的操作,但这个操作的具体实现要从参数传入,而不是写死在Request模块中),因此我们很容易想到使用回调函数来完成这个需求。
我们不难注意到这段奇怪的代码:
request.send(()-> System.out.println("[CallBack]:处理响应"));
这就是Java8之后的新特性——lambda表达式。Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),使用Lambda表达式可以使代码变的更加简洁紧凑。有了这个新特性之后,Java具有了和JavaScript等动态类型语言类似的函数传参功能,这使得我们回调函数的开发变得非常的简洁。我们将main方法中的lambda传给了Request的Runnable参数,然后在Request模块中进行调用。
从这里我们可以发现,回调函数更有利于模块化的设计。上面的例子中,Request模块中不再写死响应处理的逻辑,而这个逻辑可以从外部传入,避免了Request模块与处理逻辑的耦合。因此,回调的思想也广泛用于各种知名框架中,最大的目的就是解耦。
(二)过滤器与回调
为什么说过滤器是基于回调函数实现的?我们先来看一张关于过滤器链的图:
我们先来看源码:
public interface FilterChain { public void doFilter(ServletRequest request, ServletResponse response); }
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); } } }
public class xxFilter implements Filter{ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain){ ... filterChain.doFilter(request, response); } }
FilterChain是回调接口,doFilter(request, response)是回调方法,ApplicationFilterChain是实现类,里面能得到实现了Filter接口的实现类xxxFilter(平常我们自己写过滤器就是实现Filte接口并重写doFilter方法),在doFilter(request, response)中执行中了某个Filter实现类的doFilter(request, response, this)方法,这里的this指的当前ApplicationFilterChain类,在这个方法执行某些处理后需要回调ApplicationFilterChain.doFilter(request, response),这个回调会执行filter链中的下一个filter,一直循环到结束。因为ApplicationFilterChain是在回调方法里调用的各个filter的doFilter方法,而各个filter里又回调了ApplicationFilterChain的回调方法,所以会循环执行。
这里理解可能有点抽象,可以与上面的流程图对照来看。通过这个过程分析,我们就明白了为什么我们不执行filterChain.doFilter(request, response)方法就不能跳转到下一个过滤器。
————————————————
版权声明:本文为CSDN博主「@ Zoey」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43907422/article/details/106320430
0条评论
点击登录参与评论