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

Spring WebFlux 入门

2. Spring WebFlux Framework

Spring WebFlux有两种风格:功能性和基于注释的。基于注释的与Spring MVC非常相近。例如:

@RestController
@RequestMapping("/users")
public class MyRestController {

    @GetMapping("/{user}")
    public Mono<User> getUser(@PathVariable Long user) {
    // ...
    }

    @GetMapping("/{user}/customers")
    public Flux<Customer> getUserCustomers(@PathVariable Long user) {
    // ...
    }
    
    @DeleteMapping("/{user}")
    public Mono<User> deleteUser(@PathVariable Long user) {
    // ...
    }
} 

与之等价,也可以这样写:

@Configuration
public class RoutingConfiguration {
    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) {
        return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser)
            .andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
            .andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
    }
}

@Component
public class UserHandler {
    public Mono<ServerResponse> getUser(ServerRequest request) {
    // ...
    }
    public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
    // ...
    }
    public Mono<ServerResponse> deleteUser(ServerRequest request) {
    // ...
    }
}

如果你同时添加了spring-boot-starter-web和spring-boot-starter-webflux依赖,那么Spring Boot会自动配置Spring MVC,而不是WebFlux。你当然可以强制指定应用类型,通过SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE) 

3. Hello WebFlux

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cjs.example</groupId>
    <artifactId>cjs-reactive-rest-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cjs-reactive-rest-service</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

GreetingHandler.java 

package com.cjs.example.restservice.hello;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.util.concurrent.atomic.AtomicLong;

/**
 * @author ChengJianSheng
 * @date 2020-03-25
 */
@Component
public class GreetingHandler {

    private final AtomicLong counter = new AtomicLong();

    /**
     * A handler to handle the request and create a response
     */
    public Mono<ServerResponse> hello(ServerRequest request) {
        return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
                .body(BodyInserters.fromValue("Hello, Spring!"));

    }
}

GreetingRouter.java

package com.cjs.example.restservice.hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.*;

/**
 * @author ChengJianSheng
 * @date 2020-03-25
 */
@Configuration
public class GreetingRouter {

    /**
     * The router listens for traffic on the /hello path and returns the value provided by our reactive handler class.
     */
    @Bean
    public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {
        return RouterFunctions.route(RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), greetingHandler::hello);
    }
}

GreetingWebClient.java

package com.cjs.example.restservice.hello;

import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

/**
 * @author ChengJianSheng
 * @date 2020-03-25
 */
public class GreetingWebClient {

    /**
     * For reactive applications, Spring offers the WebClient class, which is non-blocking.
     *
     * WebClient can be used to communicate with non-reactive, blocking services, too.
     */
    private WebClient client = WebClient.create("http://localhost:8080");

    private Mono<ClientResponse> result = client.get()
            .uri("/hello")
            .accept(MediaType.TEXT_PLAIN)
            .exchange();

    public String getResult() {
        return ">> result = " + result.flatMap(res -> res.bodyToMono(String.class)).block();
    }
}

Application.java

package com.cjs.example.restservice;

import com.cjs.example.restservice.hello.GreetingWebClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author ChengJianSheng
 * @date 2020-03-25
 */
@SpringBootApplication
public class CjsReactiveRestServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(CjsReactiveRestServiceApplication.class, args);

        GreetingWebClient gwc = new GreetingWebClient();
        System.out.println(gwc.getResult());
    }

} 

可以直接在浏览器中访问 http://localhost:8080/hello  

GreetingRouterTest.java

package com.cjs.example.restservice;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GreetingRouterTest {

    @Autowired
    private WebTestClient webTestClient;

    /**
     * Create a GET request to test an endpoint
     */
    @Test
    public void testHello() {
        webTestClient.get()
                .uri("/hello")
                .accept(MediaType.TEXT_PLAIN)
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("Hello, Spring!");
    }

}

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cjs.example</groupId>
    <artifactId>cjs-webflux-hello</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cjs-webflux-hello</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.67</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

UserController.java

package com.cjs.example.webflux.controller;

import com.alibaba.fastjson.JSON;
import com.cjs.example.webflux.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ReactiveHashOperations;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

/**
 * @author ChengJianSheng
 * @date 2020-03-27
 */
@RestController
@RequestMapping("/users")
public class UserController {


    @Autowired
    private ReactiveStringRedisTemplate reactiveStringRedisTemplate;

    @GetMapping("/hello")
    public Mono<String> hello() {
        return Mono.just("Hello, Reactive");
    }

    @PostMapping("/save")
    public Mono<Boolean> saveUser(@RequestBody User user) {
        ReactiveHashOperations hashOperations = reactiveStringRedisTemplate.opsForHash();
        return hashOperations.put("USER_HS", String.valueOf(user.getId()), JSON.toJSONString(user));
    }

    @GetMapping("/info/{id}")
    public Mono<User> info(@PathVariable Integer id) {
        ReactiveHashOperations reactiveHashOperations = reactiveStringRedisTemplate.opsForHash();
        Mono<String> hval = reactiveHashOperations.get("USER_HS", String.valueOf(id));
        return hval.map(e->JSON.parseObject(e, User.class));
    }

}

CoffeeController.java

package com.cjs.example.webflux.controller;

import com.cjs.example.webflux.domain.Coffee;
import org.springframework.data.redis.core.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * Spring WebFlux is the new reactive web framework introduced in Spring Framework 5.0.
 * Unlike Spring MVC, it does not require the Servlet API, is fully asynchronous and non-blocking,
 * and implements the Reactive Streams specification through the Reactor project.
 *
 * @author ChengJianSheng
 * @date 2020-03-27
 */
@RestController
@RequestMapping("/coffees")
public class CoffeeController {

    private final ReactiveRedisOperations<String, Coffee> coffeeOps;

    public CoffeeController(ReactiveRedisOperations<String, Coffee> coffeeOps) {
        this.coffeeOps = coffeeOps;
    }

    @GetMapping("/getAll")
    public Flux<Coffee> getAll() {
        return coffeeOps.keys("*").flatMap(coffeeOps.opsForValue()::get);
    }

    @GetMapping("/info/{id}")
    public Mono<Coffee> info(@PathVariable String id) {
        ReactiveValueOperations valueOperations = coffeeOps.opsForValue();
        return valueOperations.get(id);
    }
} 

最后,也是非常重要的一点:异步非阻塞并不会使程序运行得更快。WebFlux 并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。

Spring WebFlux 是一个异步非阻塞的 Web 框架,所以,它特别适合应用在 IO 密集型的服务中,比如微服务网关这样的应用中。

Reactive and non-blocking generally do not make applications run faster.

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

相关推荐