Spring-webflux?響應(yīng)式編程的實(shí)例詳解
1. 前言
Spring 提供了兩個并行堆棧。一種是基于帶有 Spring MVC 和 Spring Data 結(jié)構(gòu)的 Servlet API。另一個是完全反應(yīng)式堆棧,它利用了 Spring WebFlux 和 Spring Data 的反應(yīng)式存儲庫。在這兩種情況下,Spring Security 都提供了對兩種堆棧的支持。
2. Spring-webflux簡介
Spring WebFlux 是在 5.0 版中添加的。它是完全無阻塞的,支持 Reactive Streams
背壓,并且可以在 Netty、Undertow 和 Servlet 3.1+ 容器等服務(wù)器上運(yùn)行。
3. 什么是“響應(yīng)式”
所謂響應(yīng)式,舉個例子,當(dāng)調(diào)用一個api獲取數(shù)據(jù)時,無需阻塞等待數(shù)據(jù)返回,而是當(dāng)有數(shù)據(jù)返回時會進(jìn)行告知??梢婍憫?yīng)式是非阻塞的,意味著調(diào)用方法后,CPU可以去做別的事情,當(dāng)接收到數(shù)據(jù)響應(yīng)時CPU再回來處理,這種方式提高了系統(tǒng)的吞吐量。
而響應(yīng)式編程,其實(shí)是為這種異步非阻塞的流式編程制定的一套標(biāo)準(zhǔn)。流式編程已不陌生了,Java8提供的stream api就是這種風(fēng)格。這套標(biāo)準(zhǔn)包括對運(yùn)行環(huán)境(JVM、JavaScript)以及網(wǎng)絡(luò)協(xié)議相關(guān)的規(guī)范。
和傳統(tǒng)的阻塞式servlet容器不一樣。響應(yīng)式容器能進(jìn)一步提高資源的利用率,避免線程長時間處于等待狀態(tài),能以較少的線程處理更多的請求,缺點(diǎn)是整個處理鏈路必須是異步的,是基于事件響應(yīng)的,不能阻塞事件線程,不然服務(wù)器性能會急劇下降,當(dāng)然spring webflux
并不能完整的替代傳統(tǒng)的阻塞式容器,可根據(jù)需求進(jìn)行選型。
應(yīng)用案例:Geteway
所有微服務(wù)的請求都會通過網(wǎng)關(guān),如果采用mvc 對于并發(fā)量有一定的瓶頸。
4. Spring-webflux的響應(yīng)式API
Spring-webflux
框架是基于Reactor
這個開源項目開發(fā)的。Reactor
框架是跟Spring
緊密配合的。
里邊提供了兩種API類型,分別是Mono
和Flux
;
Mono
表示0 或 1個元素,Flux
表示0 至 N個元素,
5. Spring MVC 還是 WebFlux?
這兩個web框架分別代表著兩種不同類型的編程流派,官方給出了一個圖作為對比如下
建議考慮以下具體點(diǎn):
- 如果您有一個運(yùn)行良好的 Spring MVC 應(yīng)用程序,則無需更改。命令式編程是編寫、理解和調(diào)試代碼的最簡單方法。您可以選擇最多的庫,因?yàn)?strong>從歷史上看,大多數(shù)都是阻塞的。
- Spring WebFlux 提供與該領(lǐng)域中其他人相同的執(zhí)行模型優(yōu)勢,并且還提供服務(wù)器選擇(Netty、Tomcat、Jetty、Undertow 和 Servlet 3.1+ 容器)、編程模型(帶注釋的控制器和功能性 Web 端點(diǎn))的選擇,以及反應(yīng)庫(Reactor、RxJava 或其他)的選擇。
- 如果您對用于 Java 8 lambda 或 Kotlin 的輕量級、功能性 Web 框架感興趣,您可以使用 Spring WebFlux 功能性 Web 端點(diǎn)。對于要求不那么復(fù)雜的小型應(yīng)用程序或微服務(wù)來說,這也是一個不錯的選擇,它們可以從更高的透明度和控制中受益。
- 在微服務(wù)架構(gòu)中,您可以混合使用帶有 Spring MVC 或 Spring WebFlux 控制器或帶有 Spring WebFlux 功能端點(diǎn)的應(yīng)用程序。在兩個框架中都支持相同的基于注釋的編程模型,可以更輕松地重用知識,同時為正確的工作選擇正確的工具。
- 評估應(yīng)用程序的一種簡單方法是檢查其依賴關(guān)系。如果您要使用阻塞持久性 API(JPA、JDBC)或網(wǎng)絡(luò) API,那么 Spring MVC 至少是常見架構(gòu)的最佳選擇。Reactor 和 RxJava 在單獨(dú)的線程上執(zhí)行阻塞調(diào)用在技術(shù)上是可行的,但您不會充分利用非阻塞 Web 堆棧。
- 如果您有一個調(diào)用遠(yuǎn)程服務(wù)的 Spring MVC 應(yīng)用程序,請嘗試響應(yīng)式WebClient. 您可以直接從 Spring MVC 控制器方法返回反應(yīng)類型(Reactor、RxJava或其他)。每個呼叫的延遲或呼叫之間的相互依賴性越大,好處就越顯著。Spring MVC 控制器也可以調(diào)用其他響應(yīng)式組件。
- 如果您有一個大型團(tuán)隊,請記住向非阻塞、函數(shù)式和聲明式編程轉(zhuǎn)變的陡峭學(xué)習(xí)曲線。在沒有完全開關(guān)的情況下啟動的一種實(shí)用方法是使用 reactive WebClient。除此之外,從小處著手并衡量收益。我們預(yù)計,對于廣泛的應(yīng)用,這種轉(zhuǎn)變是不必要的。如果您不確定要尋找什么好處,請先了解非阻塞 I/O 的工作原理(例如,單線程 Node.js 上的并發(fā)性)及其影響。
其次: webflux兼容大部分springmvc的注解,也可以像mvc那樣創(chuàng)建controller處理請求。
區(qū)別:
- WebFlux是完全異步非阻塞的,SpringMVC是同步阻塞的。
- WebFlux采用異步響應(yīng)式編程,SpringMVC采用命令式編程。
- WebFlux由于完全異步,所有操作數(shù)據(jù)庫的框架,以及數(shù)據(jù)庫也都要求是支持異步的,所以目前不支持Mybatis、不支持Oracle數(shù)據(jù)庫。
6. 并發(fā)模型
盡管webmvc
和webflux
都支持使用注解來定義一個Controller
,但是其實(shí)現(xiàn)方式完全不同。
webmvc是一個Servlet應(yīng)用,實(shí)現(xiàn)是阻塞式IO,其維護(hù)一個線程池來處理每一個用戶請求,也就是當(dāng)Servlet
容器啟動時,就會創(chuàng)建比如10個線程出來,因此系統(tǒng)吞吐量的瓶頸在于有限的連接數(shù)和阻塞的請求處理過程。
webflux可以基于netty
這樣的NIO網(wǎng)絡(luò)框架,它只需要很少的幾個工作線程(Event loop worker)就能夠處理并響應(yīng)請求。由于無需阻塞等待方法返回,CPU資源就得到了更好的利用。
webflux并不能讓程序運(yùn)行地更快;而是提高了并發(fā)處理請求的能力,即提高系統(tǒng)吞吐量。
7. webflux使用
pom依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>2.5.9</version> </dependency> </dependencies>
定義對象:
public class Person { private Integer id; private Integer age; private String name; public Person(Integer id, Integer age, String name) { this.id = id; this.age = age; this.name = name; } public Person() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } }
然后定義PersonController
,響應(yīng)式風(fēng)格中不再使用@RequestMapping
聲明地址映射,而是通過RouterFunctions.route().GET()
方法:
@Configuration public class PersonRouter { @Resource private PersonHandler personHandler; @Bean public RouterFunction<ServerResponse> personRoutes() { return RouterFunctions.route() .GET("/person/{id}", RequestPredicates.accept(MediaType.APPLICATION_JSON), personHandler::getPerson) .GET("/person", RequestPredicates.accept(MediaType.APPLICATION_JSON), personHandler::listPeople) .POST("/person", personHandler::createPerson) .build(); } }
在PersonHandler
中處理對應(yīng)的HTTP請求,等同于MVC架構(gòu)中的Service層
@Component public class PersonHandler { @Resource private IPersonDao personDao; public Mono<ServerResponse> listPeople(ServerRequest request) { Flux<Person> people = personDao.getList(); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(people, Person.class); } public Mono<ServerResponse> createPerson(ServerRequest request) { return request.bodyToMono(Person.class) .flatMap(i -> personDao.savePerson(i)) .flatMap(p -> ServerResponse.ok().bodyValue(p)); } public Mono<ServerResponse> getPerson(ServerRequest request) { int personId = Integer.parseInt(request.pathVariable("id")); return personDao.getPerson(personId) .flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)) .switchIfEmpty(ServerResponse.notFound().build()); } }
IPersonDao
public interface IPersonDao { /** * 獲取所有person * @author: yh * @date: 2022/9/4 * @return Flux<Person> */ Flux<Person> getList(); /** * 保存對象 * @param person person * @author: yh * @date: 2022/9/4 * @return Mono<Void> */ Mono<Void> savePerson(Person person); /** * 根據(jù)id查詢 * @param id id * @author: yh * @date: 2022/9/4 * @return Mono<Person> */ Mono<Person> getPerson(Integer id); }
PersonDao
@Component public class PersonDao implements IPersonDao { private final List<Person> personList = new ArrayList<>(); public PersonDao() { this.personList.add(new Person(1, 17, "張三")); this.personList.add(new Person(2, 18, "李四")); } @Override public Flux<Person> getList() { return Flux.fromIterable(personList); } @Override public Mono<Void> savePerson(Person person) { personList.add(person); System.out.println("personList.size = " + personList); return Mono.empty(); } @Override public Mono<Person> getPerson(Integer id) { return Mono.justOrEmpty(personList.stream().filter(p -> p.getId().equals(id)).findFirst()); } }
@SpringBootApplication @EnableWebFlux public class SpringWebfluxSessionApplication implements WebFluxConfigurer { public static void main(String[] args) { SpringApplication.run(SpringWebfluxSessionApplication.class, args); } }
8. 測試
通過啟動日志可以證實(shí)Spring-webflux
是默認(rèn)使用Netty
提供HTTP服務(wù)
GET請求:http://127.0.0.1:8080/person
POST請求:http://127.0.0.1:8080/person
boyd:
{ "id": 9, "age": 17, "name": "張三" }
控制臺輸出:
GET請求:http://127.0.0.1:8080/person/9
完整代碼已上傳 Gitee Spring整合常用組件
到此這篇關(guān)于Spring-webflux 響應(yīng)式編程的文章就介紹到這了,更多相關(guān)Spring webflux 響應(yīng)式編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java AtomicInteger類使用方法實(shí)例講解
這篇文章主要介紹了Java AtomicInteger類使用方法實(shí)例講解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06SpringBoot使用H2嵌入式數(shù)據(jù)庫的實(shí)例代碼
本文通過實(shí)例代碼給大家介紹了SpringBoot使用H2嵌入式數(shù)據(jù)庫的相關(guān)知識,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2021-10-10Java中的==和equals()區(qū)別小結(jié)
在Java編程中,理解==操作符和equals()方法的區(qū)別是至關(guān)重要的,本文主要介紹了Java中的==和equals()區(qū)別,具有一定的參考價值,感興趣的可以了解一下2023-08-08spring?kafka?@KafkaListener詳解與使用過程
這篇文章主要介紹了spring-kafka?@KafkaListener詳解與使用,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02Mybatis Criteria使用and和or進(jìn)行聯(lián)合條件查詢的操作方法
這篇文章主要介紹了Mybatis Criteria的and和or進(jìn)行聯(lián)合條件查詢的方法,本文通過例子給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2021-10-10淺談spring使用策略模式實(shí)現(xiàn)多種場景登錄方式
本文主要介紹了spring使用策略模式實(shí)現(xiàn)多種場景登錄方式,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12