社群精選 |手把手教你 Spring Boot 整合 Reactor

今天小編為大家帶來的是社群作者六七十三的文章,讓我們一起來學習Spring Boot 整合 Reactor。

Reactor 是一個完全非阻塞的 JVM 響應式程式設計基礎,有著高效的需求管理(背壓的形式)。它直接整合 Java8 的函式式 API,尤其是

CompletableFuture, Stream,

還有

Duration

。提供了可組合的非同步化序列 API — Flux (對於 [N] 個元素) and Mono (對於 [0|1] 元素) — 並廣泛實現 響應式Stream 規範。

這次帶大家從零開始,使用 Spring Boot 框架建立一個 Reactor 響應式專案。

1 建立專案

使用

https://start。spring。io/

建立專案。新增依賴項:H2、Lombok、Spring Web、JPA、JDBC

社群精選 |手把手教你 Spring Boot 整合 Reactor

然後匯入 Reactor 包

io。projectreactor reactor-core io。projectreactor reactor-test test

2 整合 H2 資料庫

application。properties

檔案中新增 H2 資料連線資訊。此外,埠使用 8081(隨意,本地未被使用的埠即可)。

server。port=8081################ H2 資料庫 基礎配置 ##############spring。datasource。driverClassName=org。h2。Driverspring。datasource。url=jdbc:h2:~/userspring。datasource。username=saspring。datasource。password=spring。jpa。database=h2spring。jpa。hibernate。ddl-auto=updatespring。h2。console。path=/h2-consolespring。h2。console。enable=true

3 建立測試類

3。1 user 實體

建立簡單資料操作實體 User。

import lombok。Data;import lombok。NoArgsConstructor;import javax。persistence。*;/** * @Author: prepared * @Date: 2022/8/29 21:40 */@Data@NoArgsConstructor@Table(name = “t_user”)@Entitypublic class User { @Id @GeneratedValue(strategy = GenerationType。AUTO) private Long id; private String userName; private int age; private String sex; public User(String userName, int age, String sex) { this。userName = userName; this。age = age; this。sex = sex; }}

3。2 UserRepository

資料模型層使用 JPA 框架。

import com。prepared。user。domain。User;import org。springframework。data。jpa。repository。JpaRepository;import org。springframework。stereotype。Repository;/** * @Author: prepared * @Date: 2022/8/29 21:45 */@Repositorypublic interface UserRepository extends JpaRepository {}

3。3 UserService

service 增加兩個方法,add 方法,用來新增資料;list 方法,用來查詢所有資料。所有介面返回 Mono/Flux 物件。

最佳實踐:所有的第三方介面、IO 耗時比較長的操作都可以放在 Mono 物件中。

doOnError

監控異常情況;

doFinally

監控整體執行情況,如:耗時、呼叫量監控等。

import com。prepared。user。dao。UserRepository;import com。prepared。user。domain。User;import org。slf4j。Logger;import org。slf4j。LoggerFactory;import org。springframework。stereotype。Service;import reactor。core。publisher。Mono;import javax。annotation。Resource;import java。util。List;/** * @Author: prepared * @Date: 2022/8/29 21:45 */@Servicepublic class UserService { private Logger logger = LoggerFactory。getLogger(UserService。class); @Resource private UserRepository userRepository; public Mono save(User user) { long startTime = System。currentTimeMillis(); return Mono。fromSupplier(() -> { return userRepository。save(user) != null; }) 。doOnError(e -> { // 列印異常日誌&增加監控(自行處理) logger。error(“save。user。error, user={}, e”, user, e); }) 。doFinally(e -> { // 耗時 & 整體健康 logger。info(“save。user。time={}, user={}”, user, System。currentTimeMillis() - startTime); }); } public Mono findById(Long id) { long startTime = System。currentTimeMillis(); return Mono。fromSupplier(() -> { return userRepository。getReferenceById(id); })。doOnError(e -> { // 列印異常日誌&增加監控(自行處理) logger。error(“findById。user。error, id={}, e”, id, e); }) 。doFinally(e -> { // 耗時 & 整體健康 logger。info(“findById。user。time={}, id={}”, id, System。currentTimeMillis() - startTime); }); } public Mono> list() { long startTime = System。currentTimeMillis(); return Mono。fromSupplier(() -> { return userRepository。findAll(); })。doOnError(e -> { // 列印異常日誌&增加監控(自行處理) logger。error(“list。user。error, e”, e); }) 。doFinally(e -> { // 耗時 & 整體健康 logger。info(“list。user。time={}, ”, System。currentTimeMillis() - startTime); }); } public Flux listFlux() { long startTime = System。currentTimeMillis(); return Flux。fromIterable(userRepository。findAll()) 。doOnError(e -> { // 列印異常日誌&增加監控(自行處理) logger。error(“list。user。error, e”, e); }) 。doFinally(e -> { // 耗時 & 整體健康 logger。info(“list。user。time={}, ”, System。currentTimeMillis() - startTime); }); }}

3。4 UserController

controller

增加兩個方法,add 方法,用來新增資料;list 方法,用來查詢所有資料。

list 方法還有另外一種寫法,這就涉及到 Mono 和 Flux 的不同了。

返回

List

可以使用

Mono>

,也可以使用

Flux

Mono

是一個特定的

Publisher

,最多可以發出一個元素

Flux

是一個標準的

Publisher

,表示為發出 0 到 N 個元素的非同步序列

import com。prepared。user。domain。User;import com。prepared。user。service。UserService;import org。springframework。web。bind。annotation。RequestMapping;import org。springframework。web。bind。annotation。RestController;import reactor。core。publisher。Mono;import javax。annotation。Resource;import java。util。ArrayList;import java。util。List;/** * @Author: prepared * @Date: 2022/8/29 21:47 */@RestControllerpublic class UserController { @Resource private UserService userService; @RequestMapping(“/add”) public Mono add() { User user = new User(“xiaoming”, 10, “F”); return userService。save(user) ; } @RequestMapping(“/list”) public Mono> list() { return userService。list(); }} @RequestMapping(“/listFlux”) public Flux listFlux() { return userService。listFlux(); }

3。5 SpringReactorApplication 添加註解支援

Application 啟動類添加註解 @EnableJpaRepositories

import org。springframework。boot。SpringApplication;import org。springframework。boot。autoconfigure。SpringBootApplication;import org。springframework。data。jpa。repository。config。EnableJpaRepositories;/** * Hello world! */@SpringBootApplication@EnableJpaRepositoriespublic class SpringReactorApplication { public static void main(String[] args) { SpringApplication。run(SpringReactorApplication。class, args); }}

測試

啟動專案,訪問

localhost:8081/add

,正常返回 true。

社群精選 |手把手教你 Spring Boot 整合 Reactor

查詢所有資料,訪問

localhost:8081/list

,可以看到插入的資料,已經查詢出來了。PS:我這裡執行了多次 add,所以有多條記錄。

社群精選 |手把手教你 Spring Boot 整合 Reactor

後臺日誌:

2022-09-05 20:13:17。385 INFO 15696 ——- [nio-8082-exec-2] com。prepared。user。service。UserService : list。user。time=181, 執行了 UserService list() 方法的 doFinnally 程式碼塊,列印耗時日誌。

總結

響應式程式設計的優勢是不會阻塞。那麼正常我們的程式碼中有哪些阻塞的操作呢?

Future 的 get() 方法;

Reactor 中的 block() 方法,subcribe() 方法,所以在使用 Reactor 的時候,除非編寫測試程式碼,否則不要直接呼叫以上兩個方法;

同步方法呼叫,所以高併發情況下,會使用非同步呼叫(如Future)來提升響應速度。

下一篇,講解如何將熔斷、限流框架 resilience4j 整合到專案中,敬請期待。