WebFlux前置知识(一)

江南一点雨 ... 2021-11-10 09:26:28
  • WebFlux
大约 7 分钟

[TOC]

最近太忙了,发文频率有点不稳定,理解万岁。前面和大家说了要更 WebFlux,学习 WebFlux 之前,我们先来学习一些前置知识。

Rome was not built in a day。

WebFlux 也不是一帮人拍脑门突然发明的,它是一个漫长的过程,WebFlux 本身在逐步完善,各种配套工具/理论也在逐步发展。

因此当松哥想写 WebFlux 的时候,发现没法直接从 WebFlux 本身开始写起,对于很多没有接触过函数式编程的人来说,上来就整 WebFlux 还是有一些挑战的,想来想去,我觉得还是先来和大家捋一捋 JDK8 中的一些旧玩意。

虽然 JDK8 发布距今已经七八年了,但是相信还是有相当多小伙伴用着 JDK8,写着 JDK6 的代码。所以我们有必要回顾一下 JDK8,也算是我们学习 WebFlux 的一些前置知识。

好啦,开整吧。

# 1.Lambda 表达式的四种写法

JDK8 中引入了 Lambda,这个大家都知道,虽然现在 JDK 都出到 16 了,但是老实说,项目中的 Lambda 表达式似乎还是很少有人用。有的团队技术风格激进,可能会见到很多 Lambda,但是大部分技术团队还是比较保守的。今天为了学习 WebFlux,我们还是先来回顾一下 Lambda 表达式的几种写法。

先来说说,如果要用 Lambda,必须是只有一个需要强制实现方法的接口,我们可以使用 @FunctionalInterface 注解去标记该接口:

@FunctionalInterface
interface ICalculator{
    int square(int i);
}
1
2
3
4

此时如果该接口中有多个空方法,编译期间就会报错。

现在我们建议尽量将一个接口设计的小一些,这样也满足单一职责原则。

不过 JDK8 中引入了 default 方法,就是自带默认实现的那种,自带默认实现的方法可以有多个,这个并不影响 Lambda,并且 @FunctionalInterface 注解也不会去检查默认方法的数量。

# 1.1 单个参数的

如果只是一个参数,那么直接写参数即可,例如如下代码:

interface ICalculator{
    int square(int i);
}
public class LambdaDemo01 {
    public static void main(String[] args) {
        ICalculator ic = i -> i * i;
        int square = ic.square(5);
        System.out.println("square = " + square);
    }
}
1
2
3
4
5
6
7
8
9
10

当函数只有一个参数的时候,直接写即可,不需要添加 ()

# 1.2 多个参数

多个参数的话,就需要写上 () 了,以 Spring Security 中登录成功的回调为例(不了解 Spring Security 的小伙伴可在公号后台回复 ss):

.defaultLogoutSuccessHandlerFor((req,resp,auth)->{
    resp.setContentType("application/json;charset=utf-8");
    Map<String, Object> result = new HashMap<>();
    result.put("status", 200);
    result.put("msg", "使用 logout1 注销成功!");
    ObjectMapper om = new ObjectMapper();
    String s = om.writeValueAsString(result);
    resp.getWriter().write(s);
},new AntPathRequestMatcher("/logout1","GET"))
.defaultLogoutSuccessHandlerFor((req,resp,auth)->{
    resp.setContentType("application/json;charset=utf-8");
    Map<String, Object> result = new HashMap<>();
    result.put("status", 200);
    result.put("msg", "使用 logout2 注销成功!");
    ObjectMapper om = new ObjectMapper();
    String s = om.writeValueAsString(result);
    resp.getWriter().write(s);
},new AntPathRequestMatcher("/logout2","POST"))
.and()
.csrf().disable();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这种情况,方法有多个参数,此时使用 Lambda 表达式就需要加上 ()

# 1.3 要写参数类型的

正常来说用 Lambda 时候不需要写上参数类型,但是如果你需要写,就要加上 (),还是上面那个例子,如下:

interface ICalculator{
    int square(int i);
}
public class LambdaDemo01 {
    public static void main(String[] args) {
        ICalculator ic = (int i) -> i * i;
        int square = ic.square(5);
        System.out.println("square = " + square);
    }
}
1
2
3
4
5
6
7
8
9
10

# 1.4 方法体不止一行的

如果方法体不止一行,需要用上 {},如果方法体只有一行,则不需要 {},参考上面 2、3。

# 2.函数接口

JDK8 中自带了函数式接口,使用起来也非常方便。

# 2.1基本应用

我们先来看一个简单的例子。

假设我有一个打招呼的接口 SayHello,SayHello 接口中只有一个 sayHello 方法,然后在 User 类中调用该接口对应的方法,最终用法如下:

@FunctionalInterface
interface SayHello {
    String sayHello(String name);
}

class User {
    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String say(SayHello sayHello) {
        return sayHello.sayHello(this.username);
    }
}

public class LambdaDemo02 {
    public static void main(String[] args) {
        User user = new User();
        user.setUsername("javaboy");
        String say = user.say((username) -> "hello " + username);
        System.out.println("say = " + say);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

分析 main 方法中的调用过程之后,我们发现,在调用时最核心的是如下一行代码:

(username) -> "hello " + username
1

在这段代码中,我们只关心方法的输入和输出,其他的都不是我所考虑的,为了一个简单的输入输出,我还要额外定义一个接口,这显然不太划算。

JDK8 中提供了函数接口,可以帮助我们简化上面的接口定义。如下:

class User2 {
    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String say(Function<String,String> sayHello) {
        return sayHello.apply(this.username);
    }
}
public class LambdaDemo03 {
    public static void main(String[] args) {
        User2 user2 = new User2();
        user2.setUsername("javaboy");
        String say = user2.say((username) -> "hello " + username);
        System.out.println("say = " + say);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

可以用 Function<String,String> 代替我们前面的接口定义,这里有两个泛型,第一个泛型表示接口输入的参数类型,第二个泛型表示接口输出的参数类型,而且大家注意,我们最终 main 方法中的调用方式是不变的。有了 Function 函数之后,以后我们就不需要定义一些简单的接口了。

而且 Function 函数还支持链式操作,如下:

public class LambdaDemo03 {
    public static void main(String[] args) {
        User2 user2 = new User2();
        user2.setUsername("javaboy");
        Function<String, String> func = (username) -> "hello " + username;
        String say = user2.say(func.andThen(s -> "你好 " + s));
        System.out.println("say = " + say);
    }
}
1
2
3
4
5
6
7
8
9

# 2.2 其他函数接口

接口 输入参数 返回类型 说明
UnaryOperator T T 一元函数,输入输出类型相同
Predicate T boolean 断言
Consumer T / 消费一个数据,只有输入没有输出
Function<T,R> T R 输入 T 返回 R,有输入也有输出
Supplier / T 提供一个数据,没有输入只有输出
BiFunction<T,U,R> (T,U) R 两个输入参数
BiPredicate<L, R> (L,R) boolean 两个输入参数
BiConsumer<T, U> (T,U) void 两个输入参数
BinaryOperator (T,T) T 二元函数,输入输出类型相同

接下来我们来看看这些函数接口。

# 2.2.1 UnaryOperator

当输入输出类型相同时,可以使用 UnaryOperator 函数接口,例如我们上面的代码,修改之后如下:

class User2 {
    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String say(UnaryOperator<String> sayHello) {
        return sayHello.apply(this.username);
    }
}
public class LambdaDemo03 {
    public static void main(String[] args) {
        User2 user2 = new User2();
        user2.setUsername("javaboy");
        UnaryOperator<String> func = (username) -> "helloo " + username;
        String say = user2.say(func);
        System.out.println("say = " + say);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 2.2.2 Predicate

Predicate 输入一个 T 类型的参数,输出一个 boolean 类型的值。

举一个简单的例子,例如如下代码,我们定义一个 List 集合中存放着用户姓名,现在要过滤出所有姓张的用户,代码如下:

public class LambdaDemo04 {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("张三", "里斯", "张五");
        List<String> list = names.stream().filter(s -> s.startsWith("张")).collect(Collectors.toList());
        for (String s : list) {
            System.out.println("s = " + s);
        }
    }
}
1
2
3
4
5
6
7
8
9

filter 中传入的就是一个 Predicate 函数接口,这个接口接收 String 类型的数据,返回一个 boolean。

注意

一些常用类型的函数接口,JDK 中直接提供了相关的类供我们使用,例如 Predicate<Integer> 可以用 IntPredicate 代替;Consumer<Integer> 可以用 IntConsumer 代替。

# 2.2.3 Consumer

看名字就知道,这个是消费数据,只有输入没有输出。

例如集合的遍历就可以使用 Consumer 函数接口。

public class LambdaDemo04 {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("张三", "里斯", "张五");
        names.stream().forEach(s -> System.out.println(s));
    }
}
1
2
3
4
5
6

# 2.2.4 Supplier

Supplier 刚好和 Consumer 相反,它只有输出没有输入。有的时候我们的工厂方法没有输入只有输出,这个时候就可以考虑使用 Supplier(如果有输入参数,则可以考虑使用 Function 函数接口)。

Supplier<Connection> supplier = ()->{
    Connection con = null;
    try {
        con = DriverManager.getConnection("", "", "");
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return con;
};
Connection connection = supplier.get();
1
2
3
4
5
6
7
8
9
10

# 3.小结

其实 WebFlux 前置知识还是蛮多的,今天先聊这些吧,我们后面继续。