Spring Boot 实践折腾记(11):使用 Spring 5的WebFlux快速构建效响应式REST API

关于Spring 5中的反应式编程支持Reactor类库,上一篇文章
Spring Boot 实践折腾记(10):2.0+版本中的反应式编程支持——Reactor》
已经简要介绍过,Spring 5 框架所包含的内容很多,本文还会继续介绍其中新增的 WebFlux 模块。开发人员可以使用 WebFlux 创建高性能的 Web 应用和客户端。然后,我们再结合Spring Boot 2中对Spring 5的支持,来快速构建一个响应式的Web API。

WebFlux 简介

WebFlux 模块的名称是 spring-webflux,基础类库其实还是来源于 Reactor 中对Reactive Streams规范的实现。该模块中包含了对反应式 HTTP、服务器推送事件和 WebSocket 的客户端和服务器端的支持。

WebFlux 需要底层提供运行时的支持,WebFlux 可以运行在支持 Servlet 3.1 非阻塞 IO API 的 Servlet 容器上,或是其他异步运行时环境,如 Netty 和 Undertow。Spring Boot 2默认选择的是Netty作为非阻塞 IO API 的 Servlet 容器。

在服务器端,WebFlux支持两种不同的编程模型:

  1. Spring MVC 中使用的基于 Java 注解的方式;
  2. 基于 Java 8 的 lambda 表达式的函数式编程模型。

这两种编程模型只是在代码编写方式上存在不同。它们运行在同样的反应式底层架构之上,因此在运行时是相同的,层级关系如下图所示:

这里写图片描述
要注意的是,适用于反应式的HTTP请求响应——ServerHttpRequest和ServerHttpResponse,会将请求和响应的正文转换为Flux ,而不是InputStream和OutputStream。而REST风格的JSON和XML,在序列化和反序列化时也会将正文转换为为Flux ,同理,HTML的视图渲染和服务器发送事件也会进行相应转换。

实战

下面我们通过几个简单的例子来上手实战一下响应式的API开发,Spring Boot 2已经集成了反应式的支持,我们只需要使用spring-boot-starter-webflux的启动器POM,它提供了默认为Netty支持的Spring WebFlux。 有关详细信息,还可以查看Spring Boot参考文档。

Java 注解编程模型

基于注解的编程范式,对于Java开发人员来说是非常熟悉和简单的,而在反应式编程里,同样很快就能上手,我们来看一个简单的Hello World例子。

StartAppFlux代码:

@SpringBootApplication
@RestController
public class StartAppFlux {

    public static void main(String[] args) {
        SpringApplication.run(StartAppFlux.class,args);
    }

    @GetMapping("/hello_world_mono")
    public Mono<String> helloWorld() {
        return Mono.just("Hello World with flux's mono");
    }

    @GetMapping("/hello_world_flux")
    public Flux<String> helloWorldFlux() {
        return Flux.just("Hello", "World");
    }
}

REST API

简单的例子只是刚开始,实际应用中,开发API可能才是更重要的工作之一。我们来实战一个简单的例子。该 REST API 用来对用户数据进行基本的 CRUD 操作。POJO对象Man有四个基本属性id、name、age、phone。用于操作对象的服务QueryUserServices,使用一个Map来模拟数据库的读写操作。

Man对象代码:

public class Man {

    private String id;
    private String name;
    private int age;
    private String phone;

    public Man(String id, String name, int age, String phone) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.phone = phone;
    }
//省略get、set
}

QueryUserServices代码 :

@Service
public class QueryUserServices {

    private final Map<String, Man> data = new ConcurrentHashMap<>();

    @PostConstruct
    void init(){
        Man man1 = new Man("1","mickjoust",66,"21313123132");
        Man man2 = new Man("2","mia",66,"21313123132");
        Man man3 = new Man("3","max",66,"21313123132");

        data.put(man1.getId(),man1);
        data.put(man2.getId(),man2);
        data.put(man3.getId(),man3);
    }

    Flux<Man> list() {
        return Flux.fromIterable(this.data.values());
    }

    Flux<Man> getById(final Flux<String> ids) {
        return ids.flatMap(id -> Mono.justOrEmpty(this.data.get(id)));
    }

    Mono<Man> getById(final String id) {
        return Mono.justOrEmpty(this.data.get(id))
                .switchIfEmpty(Mono.error(new RuntimeException()));
    }

    Flux<Man> createOrUpdate(final Flux<Man> mans) {
        return mans.doOnNext(man -> this.data.put(man.getId(), man));
    }

    Mono<Man> createOrUpdate(final Man man) {
        this.data.put(man.getId(), man);
        return Mono.just(man);
    }

    Mono<Man> delete(final String id) {
        return Mono.justOrEmpty(this.data.remove(id));
    }
}

WebFluxController代码:

@RestController
@RequestMapping("/")
@SpringBootApplication
public class WebFluxController {

    @Autowired
    private QueryUserServices queryUserServices;

    public static void main(String[] args) {
        SpringApplication.run(WebFluxController.class,args);
    }

    @GetMapping("/get_flux")
    public Flux<Man> getFlux(){
        return queryUserServices.list();
    }

    @GetMapping("/get_flux_ids/{ids}")
    public Flux<Man> getFluxById(@PathVariable("ids") final String idStr){
        //逗号隔开,模拟,实际可以直接传post请求
        String[] testStr = idStr.split(",");
        Flux<String> ids = Flux.fromArray(testStr);
        return queryUserServices.getById(ids);
    }

    @GetMapping("/get_mono/{id}")
    public Mono<Man> getMono(@PathVariable("id") final String id){
        return queryUserServices.getById(id);
    }

    @PostMapping("/create_flux")
    public Flux<Man> createFlux(@RequestBody final Flux<Man>  mans){
        return queryUserServices.createOrUpdate(mans);
    }

    @PostMapping("/update_mono/{id}")
    public Mono<Man> updateMono(@PathVariable("id") final String id, @RequestBody final Man man){
        Objects.requireNonNull(man);
        man.setId(id);
        return queryUserServices.createOrUpdate(man);
    }

    @GetMapping("/delete_mono/{id}")
    public Mono<Man> delete(@PathVariable("id") final String id){
        return queryUserServices.delete(id);
    }

}

函数式编程模型

在上节中介绍了基于 Java 注解的编程模型,WebFlux 还支持另一种基于 lambda 表达式的函数式编程模型。与基于 Java 注解的编程模型相比,函数式编程模型的抽象层次更低,代码编写更灵活,可以满足一些对动态性要求更高的场景。不过在编写时的代码复杂度也较高,学习曲线也较陡。开发人员可以根据实际的需要来选择合适的编程模型。目前 Spring Boot 不支持在一个应用中同时使用两种不同的编程模式。

客户端

除了服务器端实现之外,WebFlux 也提供了反应式客户端,可以访问 HTTP、SSE 和 WebSocket 服务器端。

HTTP

对于 HTTP 和 SSE,可以使用 WebFlux 模块中的类 org.springframework.web.reactive.function.client.WebClient。如下代码中的 RESTClient 用来访问前面小节中创建的 REST API。首先使用 WebClient.create 方法来创建一个新的 WebClient 对象,然后使用方法 post 来创建一个 POST 请求,并使用方法 body 来设置 POST 请求的内容。方法 exchange 的作用是发送请求并得到以 Flux表示的 HTTP 响应。最后对得到的响应进行处理并输出结果。response 的 bodyToMono 方法把响应内容转换成类 Man的对象,最终得到的结果是 Flux对象。调用 createdUser.blockFirst() 方法的作用是等待请求完成并得到所产生的类 Man 的对象。

RESTClient代码:

public class RESTClient {
    public static void main(final String[] args) {
        Man man = new Man();
        man.setId("11");
        man.setAge(33);
        man.setName("Test");
        man.setPhone("1232312313");
        WebClient client = WebClient.create("http://localhost:8080/create_flux");
        Flux<Man> createdUser = client.post()
                .uri("")
                .accept(MediaType.APPLICATION_JSON)
                .body(Mono.just(man), Man.class)
                .exchange()
                .flatMapMany(response -> response.bodyToFlux(Man.class));
        System.out.println(createdUser.blockFirst());
        //会报错Exception in thread "main" java.lang.NoClassDefFoundError: reactor/core/CoreSubscriber
    }
}

遗留问题

本来想加上函数式编程,但感觉信息量很大,后续单独加一篇文章来进行实战,同时,在客服端一节中的测试代码WebClient,会报异常,目前暂时未解决,导致了测试本身运行有点问题,后续文章解决后再更新。

小结

本文对 WebFlux 模块进行了简要介绍,主要是其中的 HTTP 支持。对于Sprng 5新增的WebFlux模块,基于Java注解模型的编程范式,是对反应式模型的一种实现,对于需要进行反应式的开发场景有很好的支持。

参考资源

1、webFlux:https://docs.spring.io/spring/docs/5.0.0.RC2/spring-framework-reference/web.html#web-reactive
2、Reactive Streams:https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams


我的其它穿越门——持续践行,我们一路同行。
头条号:说言风语
简书ID:mickjoust
公号:说言风语

猜你喜欢

转载自blog.csdn.net/mickjoust/article/details/80182234