Spring WebFlux 入门

本文内容纲要: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】响应式的、异步非阻塞的

Image

  1. 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)

  1. 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 }

  1. Reactor 核心特性

Mono: implements Publisher and returns 0 or 1 elements

Flux: implements Publisher and returns N elements

Image

Image

Image

  1. 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.

  1. Docs

https://spring.io/

https://spring.io/reactive

https://projectreactor.io/docs/core/release/reference/index.html

https://projectreactor.io/docs/core/release/reference/index.html#core-features

https://docs.spring.io/spring/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

https://www.cnblogs.com/diegodu/p/8794857.html

本文内容总结:Spring WebFlux 入门

原文链接:https://www.cnblogs.com/cjsblog/p/12580518.html

以上是 Spring WebFlux 入门 的全部内容, 来源链接: utcz.com/z/296302.html

回到顶部