WebFlux 示例程序

1.Spring WebFlux模塊

??Spring Framework 5包含一個新 spring-webflux 模塊剧蹂。該模塊包含對反應式HTTP和WebSocket客戶端的支持以及反應式服務器Web應用程序(包括REST敬矩,HTML瀏覽器和WebSocket樣式交互)毁靶。

2.Server

spring web-flux 支持2種不同的編程模型
1.支持Spring MVC @Controller 這種注解,用法大同小異
2.函數(shù)式 Java 8 lambda 風格的路由函數(shù)處理請求

WebFlux可以在支持Servlet 3.1非阻塞IO API以及其他異步運行時(如Netty和Undertow)的Servlet容器上運行。下圖顯示了服務器端兩種技術棧蝙昙,其中包括spring-webmvc模塊左側的傳統(tǒng)基于Servlet的Spring MVC以及模塊右側的 基于響應式的spring-webflux邢笙。


webflux-overview.png
1.注解式編程:雖然可以使用與Spring MVC相同的注解,但其底層核心(HandlerMapping HandlerAdapter)有所不同 ,這其中 webFlux 操作和傳遞的對象是 非阻塞式 ServletHttpRequestServletHttpResponse 而不是 傳統(tǒng) Spring MVC 所操作 阻塞式的 HttpServletRequestHttpServletResponse 對象

@RestController
public class PersonController {

    private final PersonRepository repository;

    public PersonController(PersonRepository repository) {
        this.repository = repository;
    }
    //從前臺接收json/xml類型數(shù)據(jù)
    @PostMapping("/person")
    Mono<Void> create(@RequestBody Publisher<Person> personStream) {
        return this.repository.save(personStream).then();
    }
    //得到一個集合
    @GetMapping("/person")
    Flux<Person> list() {
        return this.repository.findAll();
    }
    //RestFul 風格傳參
    @GetMapping("/person/{id}")
    Mono<Person> findById(@PathVariable String id) {
        return this.repository.findOne(id);
    }
}
2.函數(shù)式編程風格(HandlerFunctions RouterFunctions)

??2.1 HandlerFunctions http請求處理函數(shù),本質上是一個 傳入參數(shù)類型為ServerRequest 返回類型為Mono<ServerResponse>函數(shù),在Spring MVC 中與之相對應的就是被@RequestMapping 注解所修飾的方法.ServerRequest并且ServerResponse接口可以提供JDK-8(Lambda)對底層HTTP消息的友好訪問。對程序來說 響應流 暴露在FluxMono; 請求流以 Publisher作為主體才沧。
下面是對Request 和 Response操作
1.取出Request body 到 Mono<String>

Mono <String> string = request.bodyToMono(String.class);

2.取出Request body 到 Flux 如果body內容是json 或者是 xml 可以將其反序列化為相應的實體類 這里的person 假設為一個實體類

Flux <Person> people = request.bodyToFlux(Person.class)

3.bodyToMonobodyToFlux 是 ServerRequest.body(BodyExtractor)的簡寫 上面兩段代碼相當于

Mono <String> string = request.body(BodyExtractors.toMono(String.class);
Flux <Person> people = request.body(BodyExtractors.toFlux(Person.class);

4.同樣 相應ServletResponse操作類似 該對象可以 通過其內部方法build.

Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);

5.構建一個響應碼為201的 ServerResponse(自己百度201啥意思)

URI location = "http:*****";
ServerResponse.created(location).build();
  1. Hello World functionHandler (接收一個ServerRequest 返回一個ServerResponse )
HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().body(fromObject("Hello World"));

7.綜合示例

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class PersonHandler {

    private final PersonRepository repository;

    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }
    //查詢出一個 people 集合 并將這個集合 序列化到響應體中
    public Mono<ServerResponse> listPeople(ServerRequest request) { 
        Flux<Person> people = repository.allPeople();
        return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }
   //從請求體取出字符串(XML or Json 格式)反序列化為 實體對象
    public Mono<ServerResponse> createPerson(ServerRequest request) { 
        Mono<Person> person = request.bodyToMono(Person.class);
        return ServerResponse.ok().build(repository.savePerson(person));
    }
  //請求參數(shù)在路徑中 rest風格
    public Mono<ServerResponse> getPerson(ServerRequest request) { 
   //pathVariable
        int personId = Integer.valueOf(request.pathVariable("id"));
   // 404
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        Mono<Person> personMono = this.repository.getPerson(personId);
        return personMono
                .then(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
                .otherwiseIfEmpty(notFound);
    }
}
??2.2 RouterFunctions 路由函數(shù)

傳入http請求轉入到 RouterFunctions 這個路由函數(shù)中, 如果請求匹配相應的路由函數(shù),這個路由函數(shù)接收一個ServerRequest對象,并且返回一個Mono<HandlerFunction> ,如果匹配不到,則返回一個Mono<Void>

1.一般情況下我們不需要重寫一個路由函數(shù),而是使用RouterFunctions.route(RequestPredicate, HandlerFunction)這個方法來創(chuàng)建router函數(shù),其中RequestPredicate 封裝了匹配規(guī)則,HandlerFunction 則可以理解為 匹配成功之后的回調函數(shù),匹配不到的話則返回404,一般情況下我們也不需要重寫RequestPredicate,RequestPredicates,這個實現(xiàn)類(多了個s)中已經封裝了常用的匹配規(guī)則,比如:基于路徑匹配,基于HTTP請求方法類型匹配(GET.PUT.POST.DELETE..),基于內容類型匹配(Content-Type)
示例1.

RouterFunction<ServerResponse> helloWorldRoute =
    RouterFunctions.route(RequestPredicates.path("/hello-world"),//根據(jù)路徑匹配
    request -> Response.ok().body(fromObject("Hello World")));//Handler Functions 處理函數(shù)

2.路由函數(shù)的組合 如果第一個RountHandler 匹配不成功,則進入的下一個RountHandler,匹配順序按代碼順序執(zhí)行
常用方法:

1. RouterFunction.and(RouterFunction)
2. RouterFunction.andRoute(RequestPredicate, HandlerFunction) 
等價于以下的組合
2.1 RouterFunction.and()
2.2 RouterFunctions.route()

3.同路由函數(shù)的組合一樣 請求約束RequestPredicates也可以類似的組合

//and 條件
1. RequestPredicate.and(RequestPredicate)
//or 條件
2. RequestPredicate.or(RequestPredicate)
3. RequestPredicates.GET(String) 等價于以下組合
 3.1 RequestPredicates.method(HttpMethod)
 3.2 RequestPredicates.path(String)

綜合示例

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> personRoute =
    route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
        .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
        .andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);//函數(shù)引用
3.Running a Server

流程走到這就只差一步了 那就是在HTTP Server運行路由函數(shù),你可以用RouterFunctions.toHttpHandler(RouterFunction) 這個函數(shù)將路由函數(shù)轉換為一個HttpHandler,這個HttpHandler可以運行在很多種 具備響應式運行環(huán)境的容器,比如
Reactor Netty, RxNetty, Servlet 3.1+, Undertow
Reactor Netty 下示例:

//構建一個路由函數(shù)
RouterFunction<ServerResponse> route = ...
//轉換為 HttpHandler
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
//Reactor Netty 適配器
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
//構建一個簡單的Netty HTTP服務器,監(jiān)聽提供的地址和端口绍刮。
HttpServer server = HttpServer.create(HOST, PORT);
//
server.newHandler(adapter).block();

Tomcat 示例:啥玩意啊沒看懂 也不準備用Tomcat了

RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat server = new Tomcat();
Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir"));
Tomcat.addServlet(rootContext, "servlet", servlet);
rootContext.addServletMapping("/", "servlet");
tomcatServer.start();
4.HandlerFilterFunction

顧名思義這個這函數(shù)類似于 Servlet 中的 Filter ,在路由函數(shù)進行路由之前執(zhí)行,本質上也類似與一個handler function , 過濾器執(zhí)行完可以指向 handlerFunction 或者 另一個 HandlerFilterFunction(形成過濾器鏈),示例如下

import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...

RouterFunction<ServerResponse> filteredRoute =
    route.filter(request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
  });
5.Client Side
WebClient client = WebClient.create("http://example.com");
Mono<Account> account = client.get()
        .url("/accounts/{id}", 1L)
        .accept(APPLICATION_JSON)
        .exchange(request)
        .then(response -> response.bodyToMono(Account.class));

參考鏈接:
1.https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.html
2.https://coyee.com/article/12086-spring-5-reactive-web

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末温圆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子孩革,更是在濱河造成了極大的恐慌捌木,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫉戚,死亡現(xiàn)場離奇詭異刨裆,居然都是意外死亡,警方通過查閱死者的電腦和手機彬檀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門帆啃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窍帝,你說我怎么就攤上這事努潘。” “怎么了坤学?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵疯坤,是天一觀的道長。 經常有香客問我深浮,道長压怠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任飞苇,我火速辦了婚禮菌瘫,結果婚禮上蜗顽,老公的妹妹穿的比我還像新娘。我一直安慰自己雨让,他們只是感情好雇盖,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著栖忠,像睡著了一般崔挖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庵寞,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天虚汛,我揣著相機與錄音,去河邊找鬼皇帮。 笑死,一個胖子當著我的面吹牛蛋辈,可吹牛的內容都是我干的属拾。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼冷溶,長吁一口氣:“原來是場噩夢啊……” “哼渐白!你這毒婦竟也來了?” 一聲冷哼從身側響起逞频,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤纯衍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后苗胀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體襟诸,經...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年基协,在試婚紗的時候發(fā)現(xiàn)自己被綠了歌亲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡澜驮,死狀恐怖陷揪,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情杂穷,我是刑警寧澤悍缠,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站耐量,受9級特大地震影響飞蚓,放射性物質發(fā)生泄漏。R本人自食惡果不足惜廊蜒,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一玷坠、第九天 我趴在偏房一處隱蔽的房頂上張望蜗搔。 院中可真熱鬧,春花似錦八堡、人聲如沸樟凄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缝龄。三九已至,卻和暖如春挂谍,著一層夾襖步出監(jiān)牢的瞬間叔壤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工口叙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炼绘,地道東北人。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓妄田,卻偏偏與公主長得像俺亮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疟呐,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理脚曾,服務發(fā)現(xiàn),斷路器启具,智...
    卡卡羅2017閱讀 134,715評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,865評論 6 342
  • 今天開跑2018年的第一次跑步本讥,單次跑量5公里。 一 自2017年末在深圳跑完半程馬拉松后鲁冯,就將跑步擱置了起來拷沸。每...
    車馬正簡閱讀 209評論 0 2
  • 日本人牙齒不好看幾乎是世界公認的,在日本即便是明星也有很多牙齒不好的薯演。日本人牙齒的普遍難看與其他國家形成較鮮明的對...
    冷歷史觀閱讀 20,843評論 0 3
  • 偶然發(fā)現(xiàn)簡書堵漱,看了幾篇文章,近來雜亂又浮躁的心慢慢平靜下來涣仿,感謝作者們37度的文字勤庐。 做著一樣事情的人,從表面上看...
    雪名閱讀 191評論 0 1