Java后端自顶向下方法——过滤器与回调函数

Java后端自顶向下方法——过滤器与回调函数

(一)回调函数与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)方法就不能跳转到下一个过滤器。

2020年9月22日

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 鲸 设计师:meimeiellie 返回首页