关于Spring 5中的反应式编程支持Reactor类库,上一篇文章《
Spring Boot 实践折腾记(10):2.0+版本中的反应式编程支持——Reactor》已经简要介绍过,Spring 5 框架所包含的内容很多,本文还会继续介绍其中新增的 WebFlux 模块。开发人员可以使用 WebFlux 创建高性能的 Web 应用和客户端。然后,我们再结合Spring Boot 2中对Spring 5的支持,来快速构建一个响应式的Web API。
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开发人员来说是非常熟悉和简单的,而在反应式编程里,同样很快就能上手,我们来看一个简单的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 helloWorld() {
return Mono.just("Hello World with flux's mono");
}
@GetMapping("/hello_world_flux")
public Flux helloWorldFlux() {
return Flux.just("Hello", "World");
}
}
简单的例子只是刚开始,实际应用中,开发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 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 list() {
return Flux.fromIterable(this.data.values());
}
Flux getById(final Flux ids) {
return ids.flatMap(id -> Mono.justOrEmpty(this.data.get(id)));
}
Mono getById(final String id) {
return Mono.justOrEmpty(this.data.get(id))
.switchIfEmpty(Mono.error(new RuntimeException()));
}
Flux createOrUpdate(final Flux mans) {
return mans.doOnNext(man -> this.data.put(man.getId(), man));
}
Mono createOrUpdate(final Man man) {
this.data.put(man.getId(), man);
return Mono.just(man);
}
Mono 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 getFlux(){
return queryUserServices.list();
}
@GetMapping("/get_flux_ids/{ids}")
public Flux getFluxById(@PathVariable("ids") final String idStr){
//逗号隔开,模拟,实际可以直接传post请求
String[] testStr = idStr.split(",");
Flux ids = Flux.fromArray(testStr);
return queryUserServices.getById(ids);
}
@GetMapping("/get_mono/{id}")
public Mono getMono(@PathVariable("id") final String id){
return queryUserServices.getById(id);
}
@PostMapping("/create_flux")
public Flux createFlux(@RequestBody final Flux mans){
return queryUserServices.createOrUpdate(mans);
}
@PostMapping("/update_mono/{id}")
public Mono 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 delete(@PathVariable("id") final String id){
return queryUserServices.delete(id);
}
}
在上节中介绍了基于 Java 注解的编程模型,WebFlux 还支持另一种基于 lambda 表达式的函数式编程模型。与基于 Java 注解的编程模型相比,函数式编程模型的抽象层次更低,代码编写更灵活,可以满足一些对动态性要求更高的场景。不过在编写时的代码复杂度也较高,学习曲线也较陡。开发人员可以根据实际的需要来选择合适的编程模型。目前 Spring Boot 不支持在一个应用中同时使用两种不同的编程模式。
除了服务器端实现之外,WebFlux 也提供了反应式客户端,可以访问 HTTP、SSE 和 WebSocket 服务器端。
对于 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 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
公号:说言风语