學習Spring5 WebFlux這一篇就夠了

輕鬆的小希發表於2020-12-29


配套資料,免費下載
連結:https://pan.baidu.com/s/1gsHGUjRI8nPe_Gv1bZ7BMg
提取碼:5la2
複製這段內容後開啟百度網盤手機App,操作更方便哦

第九章 WebFlux

9.1、WebFlux的概述

WebFlux

Spring框架中包含的原始Web框架Spring WebMVC是專門為Servlet API和Servlet容器而構建的。響應式Web框架Spring WebFlux是在Spring 5.0以後新增的新的模組。WebFlux是一種非同步非阻塞的框架,非同步非阻塞的框架在 Servlet3.1 以後才支援,核心是基於 Reactor 的相關API實現的。WebFlux能夠在有限資源下,提高系統吞吐量和伸縮性,並以 Reactor 為基礎實現響應式程式設計,可在Netty,Undertow和Servlet 3.1+容器等伺服器上執行。這兩個Web框架都反映了其源模組的名稱(spring-webmvc和 spring-webflux),並在Spring Framework中並存。每個模組都是可選的,應用程式可以使用一個模組,也可以使用兩個模組。

非同步非阻塞

要想解釋清楚非同步非阻塞,我們就得明白,非同步和同步、阻塞和非阻塞之間的關係。

  • 非同步和同步針對呼叫者,呼叫者傳送請求,如果等著對方回應之後才去做其他事情就是同步,如果傳送請求之後不等著對方回應就去做其他事情就是非同步
  • 阻塞和非阻塞針對被呼叫者,被呼叫者收到請求之後,做完請求任務之後才給出反饋就是阻塞,收到請求之後馬上給出反饋然後再去做事情就是非阻塞

瞭解了非同步非阻塞,我們來說一說Spring WebMVC 和 Spring WebFlux分別屬於哪種?

【spring-webmvc + Servlet + Tomcat】命令式的、同步阻塞的

【spring-webflux + Reactor + Netty】響應式的、非同步非阻塞的

響應式程式設計

響應式程式設計是一種面向資料流和變化傳播的程式設計正規化。這意味著可以在程式語言中很方便地表達靜態或動態的資料流,而相關的計算模型會自動將變化的值通過資料流進行傳播。電子表格程式就是響應式程式設計的一個例子。單元格可以包含字面值或類似"=B1+C1"的公式,而包含公式的單元格的值會依據其他單元格的值的變化而變化。

Reactive 和 Reactor

Reactor 是基於Reactive Streams 規範的第四代響應庫,用於在JVM上構建非阻塞的應用程式。Reactor是Spring WebFlux的首選響應庫。它提供了 Mono和 Flux API型別,並通過豐富運算子集來處理0…1(Mono)和0…N(Flux)資料序列。因此,我們瞭解到 Reactive 是一種響應式程式設計的規範,而 Reactor 是此規範的一種具體實現,他是支撐 WebFlux 實現響應式程式設計的基礎。

到底用Spring WebMVC還是Spring WebFlux?

一個自然的問題要問,但建立了不合理的二分法。實際上,兩者共同努力擴大了可用選項的範圍。兩者的設計旨在實現彼此的連續性和一致性,它們可以並行使用,並且來自雙方的反饋對雙方都有利。下圖顯示了兩者之間的關係,它們的共同點以及各自的獨特支援:

Spring MVC和Webflux Venn

我們建議您考慮以下幾點:

  • 如果您有執行正常的Spring MVC應用程式,則無需更改。指令式程式設計是編寫,理解和除錯程式碼的最簡單方法。您有最大的庫選擇空間,因為從歷史上看,大多數庫都是阻塞的。

  • 如果您已經在購買無阻塞的Web堆疊,Spring WebFlux可以提供與該領域其他伺服器相同的執行模型優勢,還可以選擇伺服器(Netty,Tomcat,Jetty,Undertow和Servlet 3.1+容器),選擇程式設計模型(帶註釋的控制器和功能性Web端點),以及選擇反應式庫(Reactor,RxJava或其他)。

  • 如果您對與Java 8 lambda或Kotlin一起使用的輕量級功能性Web框架感興趣,則可以使用Spring WebFlux功能性Web端點。對於要求較低複雜性的較小應用程式或微服務(可以受益於更高的透明度和控制)而言,這也是一個不錯的選擇。

  • 在微服務架構中,您可以混合使用帶有Spring MVC或Spring WebFlux控制器或帶有Spring WebFlux功能端點的應用程式。在兩個框架中都支援相同的基於註釋的程式設計模型,這使得重用知識變得更加容易,同時還為正確的工作選擇了正確的工具。

  • 評估應用程式的一種簡單方法是檢查其依賴關係。如果您要使用阻塞性永續性API(JPA,JDBC)或網路API,則Spring MVC至少是通用體系結構的最佳選擇。使用Reactor和RxJava在單獨的執行緒上執行阻塞呼叫在技術上是可行的,但您不會充分利用非阻塞Web堆疊。

  • 如果您的Spring MVC應用程式具有對遠端服務的呼叫,請嘗試使用active WebClient。您可以直接從Spring MVC控制器方法返回反應型別(Reactor,RxJava或其他)。每個呼叫的等待時間或呼叫之間的相互依賴性越大,好處就越明顯。Spring MVC控制器也可以呼叫其他反應式元件。

  • 如果您有龐大的團隊,請牢記向無阻塞,功能性和宣告性程式設計的過渡過程中的學習曲線很陡。在沒有完全切換的情況下啟動的一種實用方法是使用WebClient。除此之外,從小處著手並衡量收益。我們希望,對於廣泛的應用程式,這種轉變是不必要的。如果不確定要尋找什麼好處,請先了解無阻塞I / O的工作原理(例如,單執行緒Node.js上的併發性)及其影響。

9.2、WebFlux的基礎

9.2.1、兩個核心類

響應式程式設計操作中,Reactor 是滿足 Reactive 規範的框架,Reactor 有兩個核心類,Mono 和 Flux,這兩個類實現介面 Publisher,提供豐富操作符。Flux 物件實現釋出者,返回 N 個元素;Mono 實現釋出者,返回 0 或者 1 個元素,Flux 和 Mono 都是資料流的釋出者,使用 Flux 和 Mono 都可以發出三種資料訊號: 元素值,錯誤訊號,完成訊號,錯誤訊號和完成訊號都代表終止訊號,終止訊號用於告訴訂閱者資料流結束了,錯誤訊號終止資料流同時把錯誤資訊傳遞給訂閱者。

三種訊號特點:

  • 錯誤訊號和完成訊號都是終止訊號,不能共存
  • 如果沒有傳送任何元素值,而是直接傳送錯誤或者完成訊號,表示是空資料流
  • 如果沒有錯誤訊號,沒有完成訊號,表示是無限資料流

一個入門案例:

專案名稱: reactor-demo

引入依賴:

<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core -->
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.4.1</version>
</dependency>

宣告資料流:

//第一種形式
Flux.just(1, 2, 3, 4);
Mono.just(1);
//第二種形式
Integer[] array = {1, 2, 3, 4};
Flux.fromArray(array);
//第三種形式
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
//第四種形式
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);

訂閱資料流: 呼叫 just 或者其他方法只是宣告資料流,資料流並沒有發出,只有進行訂閱之後才會觸發資料流,不訂閱什麼都不會發生的

//第一種形式
Flux.just(1, 2, 3, 4).subscribe(System.out::println);
Mono.just(1).subscribe(System.out::println);

9.2.2、四種操作符

操作符就是對資料進行一道又一道的操作,就好比生產車間的流水線,常見的操作符有四種:map、flatMap、filter、zipWith

map:將元素對映為一個新元素

1 map操作符

flatMap:把每個元素轉換成流,把轉換之後多個流合併成大的流

2 flatMap操作符

filter:可以對元素進行篩選

3 filter

zip:可以對元素進行合併

4 zip

9.3、WebFlux的註解式程式設計模型

專案名稱: webflux-demo-01

image-20201228141205060

image-20201228141639409

image-20201228141730530

image-20201228141750063

pom.xml

<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>
    </dependency>
</dependencies>

application.properties

server.port=8080

com.caochenlei.webfluxdemo01.entity.User

public class User {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;

    public User() {
    }

    public User(Integer id, String name, String gender, Integer age) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

com.caochenlei.webfluxdemo01.dao.UserDao

public interface UserDao {
    //查詢一個使用者
    public User findOne(Integer id);

    //查詢全部使用者
    public Collection<User> findAll();

    //新增一個使用者
    public void save(User user);

    //刪除一個使用者
    public void delete(User user);

    //更新一個使用者
    public void update(User user);
}

com.caochenlei.webfluxdemo01.dao.impl.UserDaoImpl

Repository
public class UserDaoImpl implements UserDao {
    //模擬資料庫中的資料
    private final Map<Integer, User> users = new ConcurrentHashMap<>();

    //呼叫無參構造初始化
    public UserDaoImpl() {
        users.put(1, new User(1, "張三", "男", 18));
        users.put(2, new User(2, "李四", "女", 19));
        users.put(3, new User(3, "王五", "男", 20));
    }

    @Override
    public User findOne(Integer id) {
        return users.get(id);
    }

    @Override
    public Collection<User> findAll() {
        return users.values();
    }

    @Override
    public void save(User user) {
        int id = users.size() + 1;
        user.setId(id);
        users.put(id, user);
    }

    @Override
    public void delete(User user) {
        users.remove(user.getId());
    }

    @Override
    public void update(User user) {
        int id = user.getId();
        users.remove(id);
        users.put(id, user);
    }
}

com.caochenlei.webfluxdemo01.service.UserService

public interface UserService {
    //查詢一個使用者
    public Mono<User> findOne(Integer id);

    //查詢全部使用者
    public Flux<User> findAll();

    //新增一個使用者
    public Mono<Void> save(Mono<User> userMono);

    //刪除一個使用者
    public Mono<Void> delete(Mono<User> userMono);

    //更新一個使用者
    public Mono<Void> update(Mono<User> userMono);
}

com.caochenlei.webfluxdemo01.service.UserServiceImpl

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public Mono<User> findOne(Integer id) {
        return Mono.justOrEmpty(userDao.findOne(id));
    }

    @Override
    public Flux<User> findAll() {
        return Flux.fromIterable(userDao.findAll());
    }

    @Override
    public Mono<Void> save(Mono<User> userMono) {
        return userMono.doOnNext(user -> {
            //儲存一個使用者
            userDao.save(user);
        }).thenEmpty(Mono.empty());
    }

    @Override
    public Mono<Void> delete(Mono<User> userMono) {
        return userMono.doOnNext(user -> {
            //刪除一個使用者
            userDao.delete(user);
        }).thenEmpty(Mono.empty());
    }

    @Override
    public Mono<Void> update(Mono<User> userMono) {
        return userMono.doOnNext(user -> {
            //更新一個使用者
            userDao.update(user);
        }).thenEmpty(Mono.empty());
    }
}

com.caochenlei.webfluxdemo01.controller.UserController

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    //查詢一個使用者
    @GetMapping("/findOne/{id}")
    public Mono<User> findOne(@PathVariable Integer id) {
        return userService.findOne(id);
    }

    //查詢全部使用者
    @GetMapping("/findAll")
    public Flux<User> findAll() {
        return userService.findAll();
    }

    //新增一個使用者
    @PostMapping("/save")
    public Mono<Void> save(@RequestBody User user) {
        Mono<User> userMono = Mono.just(user);
        return userService.save(userMono);
    }

    //刪除一個使用者
    @PostMapping("/delete")
    public Mono<Void> delete(@RequestBody User user) {
        Mono<User> userMono = Mono.just(user);
        return userService.delete(userMono);
    }

    //更新一個使用者
    @PostMapping("/update")
    public Mono<Void> update(@RequestBody User user) {
        Mono<User> userMono = Mono.just(user);
        return userService.update(userMono);
    }
}

com.caochenlei.webfluxdemo01.WebfluxDemo01Application,啟動主函式

@SpringBootApplication
public class WebfluxDemo01Application {

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

}

查詢一個:http://localhost:8080/user/findOne/1

image-20201228155334194

查詢全部:http://localhost:8080/user/findAll

image-20201228155400709

新增一個:http://localhost:8080/user/save

image-20201228155443061

image-20201228155527213

刪除一個:http://localhost:8080/user/delete

image-20201228155627264

image-20201228155647022

修改一個:http://localhost:8080/user/update

image-20201228155801745

image-20201228155821462

9.4、WebFlux的函數語言程式設計模型

專案名稱: webflux-demo-02

image-20201228141205060

image-20201228160948417

image-20201228141730530

image-20201228161020190

pom.xml

<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>
    </dependency>
</dependencies>

application.properties

server.port=8080

com.caochenlei.webfluxdemo01.entity.User

public class User {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;

    public User() {
    }

    public User(Integer id, String name, String gender, Integer age) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

com.caochenlei.webfluxdemo01.dao.UserDao

public interface UserDao {
    //查詢一個使用者
    public User findOne(Integer id);

    //查詢全部使用者
    public Collection<User> findAll();

    //新增一個使用者
    public void save(User user);

    //刪除一個使用者
    public void delete(User user);

    //更新一個使用者
    public void update(User user);
}

com.caochenlei.webfluxdemo01.dao.impl.UserDaoImpl

Repository
public class UserDaoImpl implements UserDao {
    //模擬資料庫中的資料
    private final Map<Integer, User> users = new ConcurrentHashMap<>();

    //呼叫無參構造初始化
    public UserDaoImpl() {
        users.put(1, new User(1, "張三", "男", 18));
        users.put(2, new User(2, "李四", "女", 19));
        users.put(3, new User(3, "王五", "男", 20));
    }

    @Override
    public User findOne(Integer id) {
        return users.get(id);
    }

    @Override
    public Collection<User> findAll() {
        return users.values();
    }

    @Override
    public void save(User user) {
        int id = users.size() + 1;
        user.setId(id);
        users.put(id, user);
    }

    @Override
    public void delete(User user) {
        users.remove(user.getId());
    }

    @Override
    public void update(User user) {
        int id = user.getId();
        users.remove(id);
        users.put(id, user);
    }
}

com.caochenlei.webfluxdemo01.service.UserService

public interface UserService {
    //查詢一個使用者
    public Mono<User> findOne(Integer id);

    //查詢全部使用者
    public Flux<User> findAll();

    //新增一個使用者
    public Mono<Void> save(Mono<User> userMono);

    //刪除一個使用者
    public Mono<Void> delete(Mono<User> userMono);

    //更新一個使用者
    public Mono<Void> update(Mono<User> userMono);
}

com.caochenlei.webfluxdemo01.service.UserServiceImpl

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public Mono<User> findOne(Integer id) {
        return Mono.justOrEmpty(userDao.findOne(id));
    }

    @Override
    public Flux<User> findAll() {
        return Flux.fromIterable(userDao.findAll());
    }

    @Override
    public Mono<Void> save(Mono<User> userMono) {
        return userMono.doOnNext(user -> {
            //儲存一個使用者
            userDao.save(user);
        }).thenEmpty(Mono.empty());
    }

    @Override
    public Mono<Void> delete(Mono<User> userMono) {
        return userMono.doOnNext(user -> {
            //刪除一個使用者
            userDao.delete(user);
        }).thenEmpty(Mono.empty());
    }

    @Override
    public Mono<Void> update(Mono<User> userMono) {
        return userMono.doOnNext(user -> {
            //更新一個使用者
            userDao.update(user);
        }).thenEmpty(Mono.empty());
    }
}

com.caochenlei.webfluxdemo01.handler.UserHandler

@Component
public class UserHandler {
    @Autowired
    private UserService userService;

    //查詢一個使用者
    public Mono<ServerResponse> findOne(ServerRequest request) {
        //獲取id值
        int id = Integer.valueOf(request.pathVariable("id"));
        Mono<User> userMono = userService.findOne(id);
        return ServerResponse
                .ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(userMono, User.class);
    }

    //查詢全部使用者
    public Mono<ServerResponse> findAll(ServerRequest request) {
        Flux<User> userFlux = userService.findAll();
        return ServerResponse
                .ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(userFlux, User.class);
    }

    //新增一個使用者
    public Mono<ServerResponse> save(ServerRequest request) {
        //獲取user值
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(userService.save(userMono));
    }

    //刪除一個使用者
    public Mono<ServerResponse> delete(ServerRequest request) {
        //獲取user值
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(userService.delete(userMono));
    }

    //更新一個使用者
    public Mono<ServerResponse> update(ServerRequest request) {
        //獲取user值
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(userService.update(userMono));
    }
}

com.caochenlei.webfluxdemo01.routers.RouterConfig

@Configuration
public class RouterConfig {
    @Bean
    public RouterFunction<ServerResponse> userRouter(UserHandler userHandler) {
        return RouterFunctions
                .route(GET("/user/findOne/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::findOne)
                .andRoute(GET("/user/findAll").and(accept(MediaType.APPLICATION_JSON)), userHandler::findAll)
                .andRoute(POST("/user/save").and(accept(MediaType.APPLICATION_JSON)), userHandler::save)
                .andRoute(POST("/user/delete").and(accept(MediaType.APPLICATION_JSON)), userHandler::delete)
                .andRoute(POST("/user/update").and(accept(MediaType.APPLICATION_JSON)), userHandler::update);
    }
}

com.caochenlei.webfluxdemo01.WebfluxDemo02Application,啟動主函式

@SpringBootApplication
public class WebfluxDemo02Application {

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

}

查詢一個:http://localhost:8080/user/findOne/1

image-20201228155334194

查詢全部:http://localhost:8080/user/findAll

image-20201228155400709

新增一個:http://localhost:8080/user/save

image-20201228155443061

image-20201228155527213

刪除一個:http://localhost:8080/user/delete

image-20201228155627264

image-20201228155647022

修改一個:http://localhost:8080/user/update

image-20201228155801745

image-20201228155821462

9.5、WebFlux的WebClient程式設計

本章前提: 需要 webflux-demo-02 處於啟動狀態,我們需要手動程式設計實現訪問路由

專案名稱: webflux-demo-03

image-20201228183305114

image-20201228183401676

image-20201228183514341

image-20201228183527201

image-20201228183546313

pom.xml

<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>
    </dependency>
</dependencies>

com.caochenlei.webfluxdemo03.entity.User

public class User {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;

    public User() {
    }

    public User(Integer id, String name, String gender, Integer age) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }
}

com.caochenlei.webfluxdemo03.WebClientDemo

public class WebClientDemo {
    public static void main(String[] args) {
        //建立Web客戶端
        WebClient webClient = WebClient.create("http://localhost:8080");
        
        //查詢指定使用者
        User user = webClient
                .get()
                .uri("/user/findOne/{id}", 2)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(User.class)
                .block();
        System.out.println(user);
        
        //查詢所有使用者
        Flux<User> userFlux = webClient
                .get()
                .uri("/user/findAll")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToFlux(User.class);
        userFlux.buffer().doOnNext(System.out::println).blockFirst();
        
        //新增一個使用者
        User user1 = new User(null, "趙六", "男", 16);
        String returnValue1 = webClient
                .post()
                .uri("/user/save")
                .body(Mono.just(user1), User.class)
                .retrieve()
                .bodyToMono(String.class)
                .block();
        System.out.println(returnValue1);
        
        //刪除一個使用者
        User user2 = new User();
        user2.setId(4);
        String returnValue2 = webClient
                .post()
                .uri("/user/delete")
                .body(Mono.just(user2), User.class)
                .retrieve()
                .bodyToMono(String.class)
                .block();
        System.out.println(returnValue2);
        
        //修改一個使用者
        User user3 = user;
        user3.setName("李思思");
        String returnValue3 = webClient
                .post()
                .uri("/user/update")
                .body(Mono.just(user3), User.class)
                .retrieve()
                .bodyToMono(String.class)
                .block();
        System.out.println(returnValue3);
    }
}

相關文章