失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Spring Boot 实践折腾记(11):使用 Spring 5的WebFlux快速构建效响应式REST API

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

时间:2019-11-07 14:33:57

相关推荐

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支持两种不同的编程模型:

Spring MVC 中使用的基于 Java 注解的方式;基于 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@RestControllerpublic 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代码 :

@Servicepublic class QueryUserServices {private final Map<String, Man> data = new ConcurrentHashMap<>();@PostConstructvoid 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("/")@SpringBootApplicationpublic class WebFluxController {@Autowiredprivate 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:/reactive-streams/reactive-streams-jvm#reactive-streams

我的其它穿越门——持续践行,我们一路同行。

头条号:说言风语

简书ID:mickjoust

公号:说言风语

如果觉得《Spring Boot 实践折腾记(11):使用 Spring 5的WebFlux快速构建效响应式REST API》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。