Spring WebFlux 入门
1. webflux介绍
spring webflux 是 spring framework 5.0中引入的新的响应式web框架。与spring mvc不同,它不需要servlet api,是完全异步且非阻塞的,并且通过reactor项目实现了reactive streams规范。
spring webflux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。
(ps:所谓异步非阻塞是针对服务端而言的,是说服务端可以充分利用cpu资源去做更多事情,这与客户端无关,客户端该怎么请求还是怎么请求。)
reactive streams是一套用于构建高吞吐量、低延迟应用的规范。而reactor项目是基于这套规范的实现,它是一个完全非阻塞的基础,且支持背压。spring webflux基于reactor实现了完全异步非阻塞的一套web框架,是一套响应式堆栈。
【spring-webmvc + servlet + tomcat】响应式的、异步非阻塞的
【spring-webflux + reactor + netty】命令式的、同步阻塞的
2. spring webflux framework
spring webflux有两种风格:功能性和基于注释的。基于注释的与spring mvc非常相近。例如:
1 @restcontroller
2 @requestmapping("/users")
3 public class myrestcontroller {
4
5 @getmapping("/{user}")
6 public mono<user> getuser(@pathvariable long user) {
7 // ...
8 }
9
10 @getmapping("/{user}/customers")
11 public flux<customer> getusercustomers(@pathvariable long user) {
12 // ...
13 }
14
15 @deletemapping("/{user}")
16 public mono<user> deleteuser(@pathvariable long user) {
17 // ...
18 }
19 }
与之等价,也可以这样写:
1 @configuration
2 public class routingconfiguration {
3 @bean
4 public routerfunction<serverresponse> monorouterfunction(userhandler userhandler) {
5 return route(get("/{user}").and(accept(application_json)), userhandler::getuser)
6 .androute(get("/{user}/customers").and(accept(application_json)), userhandler::getusercustomers)
7 .androute(delete("/{user}").and(accept(application_json)), userhandler::deleteuser);
8 }
9 }
10
11 @component
12 public class userhandler {
13 public mono<serverresponse> getuser(serverrequest request) {
14 // ...
15 }
16 public mono<serverresponse> getusercustomers(serverrequest request) {
17 // ...
18 }
19 public mono<serverresponse> deleteuser(serverrequest request) {
20 // ...
21 }
22 }
如果你同时添加了spring-boot-starter-web和spring-boot-starter-webflux依赖,那么spring boot会自动配置spring mvc,而不是webflux。你当然可以强制指定应用类型,通过springapplication.setwebapplicationtype(webapplicationtype.reactive)
3. hello webflux
pom.xml
1 <?xml version="1.0" encoding="utf-8"?>
2 <project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
3 xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelversion>4.0.0</modelversion>
5 <parent>
6 <groupid>org.springframework.boot</groupid>
7 <artifactid>spring-boot-starter-parent</artifactid>
8 <version>2.2.5.release</version>
9 <relativepath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupid>com.cjs.example</groupid>
12 <artifactid>cjs-reactive-rest-service</artifactid>
13 <version>0.0.1-snapshot</version>
14 <name>cjs-reactive-rest-service</name>
15
16 <properties>
17 <java.version>1.8</java.version>
18 </properties>
19
20 <dependencies>
21 <dependency>
22 <groupid>org.springframework.boot</groupid>
23 <artifactid>spring-boot-starter-webflux</artifactid>
24 </dependency>
25
26 <dependency>
27 <groupid>org.springframework.boot</groupid>
28 <artifactid>spring-boot-starter-test</artifactid>
29 <scope>test</scope>
30 <exclusions>
31 <exclusion>
32 <groupid>org.junit.vintage</groupid>
33 <artifactid>junit-vintage-engine</artifactid>
34 </exclusion>
35 </exclusions>
36 </dependency>
37 <dependency>
38 <groupid>io.projectreactor</groupid>
39 <artifactid>reactor-test</artifactid>
40 <scope>test</scope>
41 </dependency>
42 </dependencies>
43
44 <build>
45 <plugins>
46 <plugin>
47 <groupid>org.springframework.boot</groupid>
48 <artifactid>spring-boot-maven-plugin</artifactid>
49 </plugin>
50 </plugins>
51 </build>
52
53 </project>
greetinghandler.java
1 package com.cjs.example.restservice.hello;
2
3 import org.springframework.http.mediatype;
4 import org.springframework.stereotype.component;
5 import org.springframework.web.reactive.function.bodyinserters;
6 import org.springframework.web.reactive.function.server.serverrequest;
7 import org.springframework.web.reactive.function.server.serverresponse;
8 import reactor.core.publisher.mono;
9
10 import java.util.concurrent.atomic.atomiclong;
11
12 /**
13 * @author chengjiansheng
14 * @date 2020-03-25
15 */
16 @component
17 public class greetinghandler {
18
19 private final atomiclong counter = new atomiclong();
20
21 /**
22 * a handler to handle the request and create a response
23 */
24 public mono<serverresponse> hello(serverrequest request) {
25 return serverresponse.ok().contenttype(mediatype.text_plain)
26 .body(bodyinserters.fromvalue("hello, spring!"));
27
28 }
29 }
greetingrouter.java
1 package com.cjs.example.restservice.hello;
2
3 import org.springframework.context.annotation.bean;
4 import org.springframework.context.annotation.configuration;
5 import org.springframework.http.mediatype;
6 import org.springframework.web.reactive.function.server.*;
7
8 /**
9 * @author chengjiansheng
10 * @date 2020-03-25
11 */
12 @configuration
13 public class greetingrouter {
14
15 /**
16 * the router listens for traffic on the /hello path and returns the value provided by our reactive handler class.
17 */
18 @bean
19 public routerfunction<serverresponse> route(greetinghandler greetinghandler) {
20 return routerfunctions.route(requestpredicates.get("/hello").and(requestpredicates.accept(mediatype.text_plain)), greetinghandler::hello);
21 }
22 }
greetingwebclient.java
1 package com.cjs.example.restservice.hello;
2
3 import org.springframework.http.mediatype;
4 import org.springframework.web.reactive.function.client.clientresponse;
5 import org.springframework.web.reactive.function.client.webclient;
6 import reactor.core.publisher.mono;
7
8 /**
9 * @author chengjiansheng
10 * @date 2020-03-25
11 */
12 public class greetingwebclient {
13
14 /**
15 * for reactive applications, spring offers the webclient class, which is non-blocking.
16 *
17 * webclient can be used to communicate with non-reactive, blocking services, too.
18 */
19 private webclient client = webclient.create("http://localhost:8080");
20
21 private mono<clientresponse> result = client.get()
22 .uri("/hello")
23 .accept(mediatype.text_plain)
24 .exchange();
25
26 public string getresult() {
27 return ">> result = " + result.flatmap(res -> res.bodytomono(string.class)).block();
28 }
29 }
application.java
1 package com.cjs.example.restservice;
2
3 import com.cjs.example.restservice.hello.greetingwebclient;
4 import org.springframework.boot.springapplication;
5 import org.springframework.boot.autoconfigure.springbootapplication;
6
7 /**
8 * @author chengjiansheng
9 * @date 2020-03-25
10 */
11 @springbootapplication
12 public class cjsreactiverestserviceapplication {
13
14 public static void main(string[] args) {
15 springapplication.run(cjsreactiverestserviceapplication.class, args);
16
17 greetingwebclient gwc = new greetingwebclient();
18 system.out.println(gwc.getresult());
19 }
20
21 }
可以直接在浏览器中访问 http://localhost:8080/hello
greetingroutertest.java
1 package com.cjs.example.restservice;
2
3 import org.junit.jupiter.api.test;
4 import org.junit.jupiter.api.extension.extendwith;
5 import org.springframework.beans.factory.annotation.autowired;
6 import org.springframework.boot.test.context.springboottest;
7 import org.springframework.http.mediatype;
8 import org.springframework.test.context.junit.jupiter.springextension;
9 import org.springframework.test.web.reactive.server.webtestclient;
10
11 @extendwith(springextension.class)
12 @springboottest(webenvironment = springboottest.webenvironment.random_port)
13 public class greetingroutertest {
14
15 @autowired
16 private webtestclient webtestclient;
17
18 /**
19 * create a get request to test an endpoint
20 */
21 @test
22 public void testhello() {
23 webtestclient.get()
24 .uri("/hello")
25 .accept(mediatype.text_plain)
26 .exchange()
27 .expectstatus().isok()
28 .expectbody(string.class).isequalto("hello, spring!");
29 }
30
31 }
4. reactor 核心特性
mono: implements publisher and returns 0 or 1 elements
flux: implements publisher and returns n elements
5. spring data redis
pom.xml
1 <?xml version="1.0" encoding="utf-8"?>
2 <project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
3 xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelversion>4.0.0</modelversion>
5 <parent>
6 <groupid>org.springframework.boot</groupid>
7 <artifactid>spring-boot-starter-parent</artifactid>
8 <version>2.2.6.release</version>
9 <relativepath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupid>com.cjs.example</groupid>
12 <artifactid>cjs-webflux-hello</artifactid>
13 <version>0.0.1-snapshot</version>
14 <name>cjs-webflux-hello</name>
15
16 <properties>
17 <java.version>1.8</java.version>
18 </properties>
19
20 <dependencies>
21 <dependency>
22 <groupid>org.springframework.boot</groupid>
23 <artifactid>spring-boot-starter-data-redis-reactive</artifactid>
24 </dependency>
25 <dependency>
26 <groupid>org.springframework.boot</groupid>
27 <artifactid>spring-boot-starter-webflux</artifactid>
28 </dependency>
29
30 <dependency>
31 <groupid>org.apache.commons</groupid>
32 <artifactid>commons-pool2</artifactid>
33 <version>2.8.0</version>
34 </dependency>
35 <dependency>
36 <groupid>com.alibaba</groupid>
37 <artifactid>fastjson</artifactid>
38 <version>1.2.67</version>
39 </dependency>
40 <dependency>
41 <groupid>org.projectlombok</groupid>
42 <artifactid>lombok</artifactid>
43 <optional>true</optional>
44 </dependency>
45 <dependency>
46 <groupid>org.springframework.boot</groupid>
47 <artifactid>spring-boot-starter-test</artifactid>
48 <scope>test</scope>
49 <exclusions>
50 <exclusion>
51 <groupid>org.junit.vintage</groupid>
52 <artifactid>junit-vintage-engine</artifactid>
53 </exclusion>
54 </exclusions>
55 </dependency>
56 <dependency>
57 <groupid>io.projectreactor</groupid>
58 <artifactid>reactor-test</artifactid>
59 <scope>test</scope>
60 </dependency>
61 </dependencies>
62
63 <build>
64 <plugins>
65 <plugin>
66 <groupid>org.springframework.boot</groupid>
67 <artifactid>spring-boot-maven-plugin</artifactid>
68 </plugin>
69 </plugins>
70 </build>
71
72 </project>
usercontroller.java
1 package com.cjs.example.webflux.controller;
2
3 import com.alibaba.fastjson.json;
4 import com.cjs.example.webflux.domain.user;
5 import org.springframework.beans.factory.annotation.autowired;
6 import org.springframework.data.redis.core.reactivehashoperations;
7 import org.springframework.data.redis.core.reactivestringredistemplate;
8 import org.springframework.web.bind.annotation.*;
9 import reactor.core.publisher.mono;
10
11 /**
12 * @author chengjiansheng
13 * @date 2020-03-27
14 */
15 @restcontroller
16 @requestmapping("/users")
17 public class usercontroller {
18
19
20 @autowired
21 private reactivestringredistemplate reactivestringredistemplate;
22
23 @getmapping("/hello")
24 public mono<string> hello() {
25 return mono.just("hello, reactive");
26 }
27
28 @postmapping("/save")
29 public mono<boolean> saveuser(@requestbody user user) {
30 reactivehashoperations hashoperations = reactivestringredistemplate.opsforhash();
31 return hashoperations.put("user_hs", string.valueof(user.getid()), json.tojsonstring(user));
32 }
33
34 @getmapping("/info/{id}")
35 public mono<user> info(@pathvariable integer id) {
36 reactivehashoperations reactivehashoperations = reactivestringredistemplate.opsforhash();
37 mono<string> hval = reactivehashoperations.get("user_hs", string.valueof(id));
38 return hval.map(e->json.parseobject(e, user.class));
39 }
40
41 }
coffeecontroller.java
1 package com.cjs.example.webflux.controller;
2
3 import com.cjs.example.webflux.domain.coffee;
4 import org.springframework.data.redis.core.*;
5 import org.springframework.web.bind.annotation.getmapping;
6 import org.springframework.web.bind.annotation.pathvariable;
7 import org.springframework.web.bind.annotation.requestmapping;
8 import org.springframework.web.bind.annotation.restcontroller;
9 import reactor.core.publisher.flux;
10 import reactor.core.publisher.mono;
11
12 /**
13 * spring webflux is the new reactive web framework introduced in spring framework 5.0.
14 * unlike spring mvc, it does not require the servlet api, is fully asynchronous and non-blocking,
15 * and implements the reactive streams specification through the reactor project.
16 *
17 * @author chengjiansheng
18 * @date 2020-03-27
19 */
20 @restcontroller
21 @requestmapping("/coffees")
22 public class coffeecontroller {
23
24 private final reactiveredisoperations<string, coffee> coffeeops;
25
26 public coffeecontroller(reactiveredisoperations<string, coffee> coffeeops) {
27 this.coffeeops = coffeeops;
28 }
29
30 @getmapping("/getall")
31 public flux<coffee> getall() {
32 return coffeeops.keys("*").flatmap(coffeeops.opsforvalue()::get);
33 }
34
35 @getmapping("/info/{id}")
36 public mono<coffee> info(@pathvariable string id) {
37 reactivevalueoperations valueoperations = coffeeops.opsforvalue();
38 return valueoperations.get(id);
39 }
40 }
最后,也是非常重要的一点:异步非阻塞并不会使程序运行得更快。webflux 并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。
spring webflux 是一个异步非阻塞的 web 框架,所以,它特别适合应用在 io 密集型的服务中,比如微服务网关这样的应用中。
reactive and non-blocking generally do not make applications run faster.
6. docs
https://docs.spring.io/spring/docs/5.1.7.release/spring-framework-reference/index.html
上一篇: SQL和NoSQL之间的区别总结