欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring WebFlux 入门

程序员文章站 2022-04-14 16:13:08
1. WebFlux介绍 Spring WebFlux 是 Spring Framework 5.0中引入的新的响应式web框架。与Spring MVC不同,它不需要Servlet API,是完全异步且非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。 Spring W ......

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】命令式的、同步阻塞的

Spring WebFlux 入门

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

Spring WebFlux 入门 

Spring WebFlux 入门 

Spring WebFlux 入门 

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

https://docs.spring.io/spring/docs/5.1.7.release/spring-framework-reference/web-reactive.html#webflux

https://docs.spring.io/spring/docs/5.1.7.release/spring-framework-reference/web-reactive.html#webflux-reactive-spring-web