情景引入
很早之前,Java就火起來(lái)了袖牙,是因?yàn)樗朴陂_(kāi)發(fā)和處理網(wǎng)絡(luò)方面的應(yīng)用舅锄。
Java有一個(gè)愛(ài)好畴蹭,就是喜歡制定規(guī)范標(biāo)準(zhǔn)鳍烁,但自己又不善于去實(shí)現(xiàn)老翘。
反倒是一些服務(wù)提供商使用它的規(guī)范標(biāo)準(zhǔn)來(lái)制造應(yīng)用服務(wù)器而賺的盆滿缽滿铺峭。
企業(yè)用戶因要使用這些應(yīng)用服務(wù)器而向提供商支付高額費(fèi)用卫键,而且也不是特別好用钓账。
一個(gè)青年才俊為了打破這種局面而奔走呼號(hào)、奮發(fā)圖強(qiáng)服协。
自我介紹
顯然,這個(gè)青年才俊就是后來(lái)的Spring唠椭。
因企業(yè)應(yīng)用大都和web相關(guān)寺庄,而Java的web標(biāo)準(zhǔn)中較核心的一部分其實(shí)就是JavaEE里的Servlet斗塘。
Spring和Servlet“相親相愛(ài)”一番后,我就來(lái)到了這個(gè)世界台猴。我的全名叫Spring MVC饱狂,這里的Spring既是我的姓也是我的“爸爸”休讳,那Servlet就是我的“媽媽”了,大家叫我MVC就行了活合。
那個(gè)年代社會(huì)很落后留晚,條件也不好告嘲,好歹我們要求也不高,求個(gè)溫飽就行了参歹。
所以我的媽媽Servlet和她的閨蜜Filter天生就是同步阻塞的泽示,包括她們同事HttpServletRequest的getParameter械筛,getPart等這些方法也都是阻塞的埋哟。
雖然我的爸爸Spring給了我23條染色體來(lái)進(jìn)行改良赤赊,但不要忘了我還從Servlet媽媽那里繼承了23條抛计,所以我也是同步阻塞的照筑。不過(guò)我的“長(zhǎng)相”已經(jīng)好看很多了吹截,因?yàn)镾pring爸爸知道,在以后的日子里凝危,除了拼實(shí)力之外波俄,顏值也是非常重要的。
因?yàn)槲覌寢孲ervlet是一個(gè)規(guī)范蛾默,我爸爸Spring是一個(gè)框架懦铺,所以我跟他們一樣,都是無(wú)法自己獨(dú)立運(yùn)行的支鸡。
所以在我們要運(yùn)行的時(shí)候冬念,必須要尋找一個(gè)特殊的“家”,通常稱它為Servlet容器,比如tomcat就算非常知名的一個(gè)。
Servlet容器熟知我極有可能阻塞當(dāng)前執(zhí)行線程像屋,所以專門量身打造。它給我準(zhǔn)備了一個(gè)非常大的線程池,里面有好多線程。每過(guò)來(lái)一個(gè)請(qǐng)求,它就扔給我一個(gè)線程,說(shuō)自己玩去吧悍汛,隨便“折騰”奉件。
好在那時(shí)美國(guó)那個(gè)叫喬布斯的家伙被自己的公司趕出去在外面“流浪”煤痕,Servlet容器為我量身打造的這種方法完全能夠勝任日常忌卤,關(guān)鍵還非常的簡(jiǎn)單棍厂。
這種小富即安的日子就這樣往前過(guò)著例驹。
兄弟出生
生命不息屎债,變化不止。隨著喬布斯推出iphone妻味,智能機(jī)瞬間大火嘉裤,全民進(jìn)入移動(dòng)互聯(lián)網(wǎng)時(shí)代犀变。激增的網(wǎng)民數(shù)量省店,給現(xiàn)有軟件架構(gòu)帶來(lái)極大的挑戰(zhàn)虚吟。
一般來(lái)說(shuō)偏塞,社會(huì)越發(fā)達(dá)蚁堤,分工越精細(xì)粒竖,對(duì)單一工種的要求就越高。
軟件也是如此券坞,在傳統(tǒng)“大塊頭”軟件表現(xiàn)的越來(lái)越格格不入的時(shí)候捡需,微服務(wù)就如一絲春風(fēng)吹了進(jìn)來(lái)损姜。
按它的指導(dǎo)原則绷蹲,將大軟件按某種方式拆分為一個(gè)個(gè)小工程太颤。小工程規(guī)模小,便于管理凰盔,而且機(jī)動(dòng)性也好,功能聚合性更好暮胧。它承受的并發(fā)應(yīng)該更高锐借。
有人覺(jué)得與微服務(wù)比起來(lái),過(guò)去使用的web服務(wù)器如tomcat略顯笨重往衷,不夠輕量級(jí)钞翔。也有人說(shuō)tomcat內(nèi)部一個(gè)請(qǐng)求一個(gè)線程這種阻塞執(zhí)行方式消耗太多線程,不太容易支撐超高并發(fā)席舍。
無(wú)論怎么說(shuō)布轿,簡(jiǎn)而言之一句話,一個(gè)全新的時(shí)代已經(jīng)到來(lái)来颤。
此時(shí)我們需要一個(gè)更加輕量級(jí)web應(yīng)用汰扭,它使用更少的硬件資源和線程,反而更容易處理高并發(fā)福铅。那么它一定是異步非阻塞的萝毛。
這樣的使命自然落到了響應(yīng)式編程的范疇上了。所以我的爸爸Spring審時(shí)度勢(shì)滑黔,在5.0之后就趕緊把我推出來(lái)了笆包。
沒(méi)錯(cuò),我就是Spring WebFlux略荡,這里的Spring既是我的姓也是我爸爸庵佣。大家可以叫我WebFlux。初來(lái)乍到汛兜,好多人都對(duì)我不熟悉巴粪,請(qǐng)容許我介紹一番。
首先這個(gè)響應(yīng)式究竟是什么意思呢粥谬?響應(yīng)式這個(gè)術(shù)語(yǔ)肛根,指的是一個(gè)編程模型,它是圍繞著對(duì)變化的反映來(lái)構(gòu)建的漏策。
如網(wǎng)絡(luò)組件用來(lái)響應(yīng)I/O事件晶通,UI控制器用來(lái)響應(yīng)鼠標(biāo)事件等等。按照這種意識(shí)的話哟玷,非阻塞就是響應(yīng)式的狮辽,對(duì)操作完成或數(shù)據(jù)可用通知事件的響應(yīng)方式。
另外一個(gè)關(guān)于響應(yīng)式的機(jī)制是非阻塞后壓巢寡。在命令式代碼中喉脖,同步阻塞調(diào)用帶有自然的后壓迫使調(diào)用者等待。
在異步代碼中抑月,它變得非常重要树叽,用來(lái)控制事件的速率,以至于不讓一個(gè)快速的事件源壓垮它的響應(yīng)者谦絮。就是響應(yīng)者能夠控制事件源發(fā)射事件的快慢题诵。
因?yàn)轫憫?yīng)式編程是非阻塞的洁仗,所以我也是非阻塞的,因此我通常運(yùn)行在非阻塞web服務(wù)器上性锭,如Netty赠潦,Undertow等。
因?yàn)槲也粫?huì)阻塞線程的執(zhí)行草冈,所以使用一個(gè)小的固定數(shù)量的線程池(event loop workers)來(lái)處理請(qǐng)求她奥。典型地,線程數(shù)與CPU的核數(shù)相同怎棱。
這里還要感謝我的姥爺Java 8哩俭,他老人家引入了lambda表達(dá)式造就了函數(shù)式編程API。這對(duì)于非阻塞應(yīng)用和連續(xù)式API來(lái)說(shuō)是一個(gè)非常棒的東西拳恋,允許以聲明的方式把異步邏輯組合起來(lái)凡资。
我感覺(jué)我的爸爸Spring已經(jīng)超越了一個(gè)框架,成為一個(gè)平臺(tái)了谬运。所以他自己并沒(méi)有親自去實(shí)現(xiàn)響應(yīng)式處理讳苦,而是為我選擇Reactor作為響應(yīng)式庫(kù)。
Reactor提供Flux和Mono類型吩谦,擁有豐富的操作符鸳谜,支持非阻塞后壓,使用函數(shù)式API來(lái)組合異步邏輯式廷。并且Reactor強(qiáng)烈聚焦于Java服務(wù)器端咐扭。它在開(kāi)發(fā)時(shí)就已經(jīng)與爸爸Spring親密協(xié)作了。
爸爸說(shuō)滑废,我也支持其它的庫(kù)如RxJava蝗肪,但看樣子似乎讓我更愛(ài)Reactor一些。
這就是我蠕趁,WebFlux薛闪,一個(gè)集天時(shí)地利于一身的幸運(yùn)兒。但你是不是已經(jīng)暈暈的啦俺陋,沒(méi)關(guān)系豁延,慢慢來(lái)。
包羅萬(wàn)象
我想腊状,大家都看出了我爸爸Spring的野心诱咏,他不僅要成為一個(gè)平臺(tái),還要建起自己的生態(tài)系統(tǒng)缴挖,豎起壁壘袋狞。
所以他的核心事業(yè)就是進(jìn)行抽象,組合和裝配,進(jìn)而包羅萬(wàn)象苟鸯。說(shuō)的掉渣一些同蜻,就是哪個(gè)技術(shù)好,就給它整合進(jìn)來(lái)早处。
為了抹平底層不同web服務(wù)器的差異湾蔓,我爸爸抽象了一個(gè)最低級(jí)別的契約接口,HttpHandler陕赃,用于響應(yīng)式HTTP請(qǐng)求的處理卵蛉。
Mono<java.lang.Void>?handle(ServerHttpRequest?request,?ServerHttpResponse?response);
它是一個(gè)通用的接口颁股,要橫跨不同的運(yùn)行時(shí)么库。它是有意設(shè)計(jì)成最小化的,只有一個(gè)方法甘有,主要唯一目的就是在不同的HTTP服務(wù)器API上面成為一個(gè)最小化的抽象诉儒。
如果想用Netty服務(wù)器的話,就基于Netty實(shí)現(xiàn)一下亏掀,同理也可以基于Undertow實(shí)現(xiàn)一下忱反,等等,只要以后有了新的服務(wù)器滤愕,都可以加進(jìn)來(lái)的温算。
顯而易見(jiàn),HttpHandler的目標(biāo)是抽象出來(lái)對(duì)不同HTTP服務(wù)器的使用间影,說(shuō)白了就是為了和底層服務(wù)器對(duì)接注竿。但由于太偏底層,不利用上層代碼使用魂贬。
為此巩割,我的爸爸又抽象出一個(gè)稍微高一點(diǎn)級(jí)別的契約接口,WebHandler付燥,用于Web請(qǐng)求處理宣谈。很明顯,WebHandler的目標(biāo)是提供web應(yīng)用中廣泛使用的通用特性键科,如Session闻丑、表單數(shù)據(jù)和附件等等,也是為了更容易和上層代碼對(duì)接勋颖。
很自然的梆掸,WebHandler是構(gòu)建于HttpHandler之上的,換句話說(shuō)WebHander的處理會(huì)通過(guò)一個(gè)適配器HttpWebHandlerAdapter最終代理給HttpHandler來(lái)執(zhí)行牙言。
WebHandler接口也只有一個(gè)方法:
Mono<java.lang.Void>?handle(ServerWebExchange?exchange);
參數(shù)類型是ServerWebExchange酸钦,可以這樣理解,你發(fā)一個(gè)請(qǐng)求,給你一個(gè)響應(yīng)卑硫,相當(dāng)于用請(qǐng)求交換了一個(gè)響應(yīng)徒恋,而且是在服務(wù)器端交換的。
其實(shí)欢伏,整個(gè)web請(qǐng)求的處理過(guò)程是一個(gè)鏈?zhǔn)降娜胝酰詈蟛攀且粋€(gè)WebHandler,它前面可以插入多個(gè)錯(cuò)誤處理器硝拧,WebExceptionHandler径筏,多個(gè)過(guò)濾器,WebFilter障陶。
這是錯(cuò)誤處理器接口:
Mono<java.lang.Void>?handle(ServerWebExchange?exchange,?java.lang.Throwable?ex);
這是過(guò)濾器接口:
Mono<java.lang.Void>?filter(ServerWebExchange?exchange,?WebFilterChain?chain);
可見(jiàn)滋恬,我的爸爸Spring的抽象能力非常強(qiáng),對(duì)下抽象一個(gè)接口抱究,抹平了不同服務(wù)器的差異恢氯。對(duì)上抽象一個(gè)接口,可以用于支撐不同的編程模型鼓寺。
都有哪些編程模型呢勋拟,請(qǐng)繼續(xù)往下看吧。
皮囊之下
上面我在介紹自己的時(shí)候使用了美顏妈候,所以諸位很難看清我的“真面目”敢靡,下面就來(lái)進(jìn)行一下自我剖析,看看真實(shí)的我苦银。
我包含一個(gè)輕量級(jí)函數(shù)式編程模型啸胧,函數(shù)被用來(lái)參與處理請(qǐng)求,它是相對(duì)于基于注解編程模型的另一種選擇墓毒,這種編程模型叫做函數(shù)式端點(diǎn)吓揪,functional endpoints,是構(gòu)建于上面提到的WebHandler之上的所计。
我是使用HandlerFunction來(lái)處理一個(gè)HTTP請(qǐng)求的柠辞,這是一個(gè)函數(shù)式接口,也稱處理函數(shù):
@FunctionalInterface
public?interface?HandlerFunction<T?extends?ServerResponse>?{
????reactor.core.publisher.Mono<T>?handle(ServerRequest?request);
}
帶有一個(gè)ServerRequest參數(shù)主胧,返回一個(gè)Mono<ServerResponse>叭首,其中request和response對(duì)象都是不可變的,HandlerFunction就等價(jià)于Controller中的@RequestMapping標(biāo)記的方法踪栋。
實(shí)際當(dāng)中焙格,請(qǐng)求很多,處理函數(shù)也很多夷都,如何知道一個(gè)請(qǐng)求過(guò)來(lái)后眷唉,該由哪個(gè)處理函數(shù)去處理呢?
這自然要用到我的另一個(gè)函數(shù)式接口RouterFunction來(lái)搞定,稱為路由函數(shù):
@FunctionalInterface
public?interface?RouterFunction<T?extends?ServerResponse>?{
????reactor.core.publisher.Mono<HandlerFunction<T>>?route(ServerRequest?request);
}
帶有一個(gè)ServerRequest參數(shù)冬阳,返回一個(gè)Mono<HandlerFunction>蛤虐。就是它把一個(gè)請(qǐng)求路由到一個(gè)HandlerFunction的,當(dāng)路由函數(shù)匹配時(shí)肝陪,就返回一個(gè)處理函數(shù)驳庭,否則返回一個(gè)空的Mono。RouterFunction等價(jià)于@RequestMapping注解氯窍,但主要不同的是路由函數(shù)提供的不僅是數(shù)據(jù)饲常,還有行為。
下面通過(guò)一些示例狼讨,來(lái)更加直觀的幫助大家認(rèn)識(shí)這兩個(gè)函數(shù)式接口贝淤。
因處理函數(shù)是函數(shù)式接口,所以可以直接用一個(gè)lambda表達(dá)式來(lái)處理請(qǐng)求熊楼,如下:
HandlerFunction<ServerResponse>?handler?=?request?->?Response.ok().body("Hello?World");
這就表示當(dāng)任何一個(gè)請(qǐng)求過(guò)來(lái)時(shí)霹娄,都返回Hello World作為響應(yīng)能犯。
在實(shí)際應(yīng)用中鲫骗,處理邏輯一般都很復(fù)雜,肯定不是一個(gè)lambda表達(dá)式能搞定的踩晶,此時(shí)希望把處理方法專門寫(xiě)到一個(gè)類里执泰,就叫處理器類,和MVC里的Controller差不多一回事渡蜻。
下面就是一個(gè)Person的處理器類:
public?class?PersonHandler?{
????public?Mono<ServerResponse>?listPeople(ServerRequest?request)?{
????????//?...
????}
????public?Mono<ServerResponse>?createPerson(ServerRequest?request)?{
????????//?...
????}
????public?Mono<ServerResponse>?getPerson(ServerRequest?request)?{
????????//?...
????}
}
此時(shí)就可以通過(guò)處理函數(shù)术吝,引用這些處理器方法了,如下:
PersonHandler?handler?=?new?PersonHandler();
HandlerFunction<ServerResponse>?list?=?handler::listPeople;
HandlerFunction<ServerResponse>?create?=?handler::createPerson;
HandlerFunction<ServerResponse>?get?=?handler::getPerson;
要想使請(qǐng)求能夠正確被路由茸苇,首先要定義好路由函數(shù)排苍,如下:
RouterFunction<ServerResponse>?route?=?RouterFunctions.route()
????.GET("/person/{id}",?get)
????.GET("/person",?list)
????.POST("/person",?create)
????.build();
它表示當(dāng)以GET方法請(qǐng)求/person/{id}時(shí),最終會(huì)由getPerson方法處理学密。當(dāng)以GET方法請(qǐng)求/person時(shí)淘衙,最后會(huì)由listPeople方法處理。同理腻暮,以POST方法請(qǐng)求/person時(shí)彤守,會(huì)由createPerson方法處理。
可見(jiàn)哭靖,一個(gè)路由函數(shù)可以包含多個(gè)路由規(guī)則具垫,實(shí)際當(dāng)中,可以定義多個(gè)路由函數(shù)试幽,這些路由函數(shù)可以組合在一起筝蚕。
路由函數(shù)是按順序計(jì)算的,如果第一個(gè)路由不匹配,計(jì)算第二個(gè)起宽,等等蔗坯。因此,把更加具體的路由放到通用路由前面是非常有意義的燎含。注意這和基于注解的不同宾濒。
怎么樣,關(guān)掉濾鏡的我是不是更加真實(shí)了屏箍。我相信你也看明白了绘梦,至少要記住,這是基于函數(shù)式的一種編程模型赴魁,叫做函數(shù)式端點(diǎn)卸奉。
雨露均沾
像我這樣的幸運(yùn)兒,你們一定以為Spring爸爸對(duì)我非常溺愛(ài)吧颖御,告訴你榄棵,確實(shí)是這樣的。不過(guò)考慮到大家伙一路走來(lái)對(duì)Spring的不離不棄潘拱,爸爸也設(shè)身處地為你們著想疹鳄。
為此,我除了支持函數(shù)式端點(diǎn)這種編程模型之外芦岂,還支持一種編程模型叫基于注解的控制器瘪弓,annotated controllers,沒(méi)錯(cuò)禽最,就是MVC里的那個(gè)腺怯。
話說(shuō)的再白一些,就是大家已經(jīng)非常熟悉的Spring MVC那套東西川无,我百分之百的完全支持呛占,妥妥的,放心使用懦趋。
但是晾虑,并不是所有的控制器方法參數(shù)都支持響應(yīng)式類型,只有一些支持愕够,如WebSession走贪,java.security.Principal,@RequestBody惑芭,HttpEntity<B>坠狡,@RequestPart等。
下面看一個(gè)示例:
@PostMapping("/")
public?String?handle(@RequestBody?Mono<MultiValueMap<String,?Part>>?parts)?{?
????//?...
}
@PostMapping("/")
public?String?handle(@RequestBody?Flux<Part>?parts)?{?
????//?...
}
@PostMapping("/accounts")
public?void?handle(@RequestBody?Mono<Account>?account)?{
????//?...
}
不過(guò)對(duì)于控制器方法的所有返回值遂跟,都是支持響應(yīng)式類型的逃沿。
各有千秋
Spring MVC和Spring WebFlux可以一起使用婴渡,從設(shè)計(jì)上講,它們互為繼續(xù)凯亮、互為一致边臼。
它們的關(guān)系,請(qǐng)看下圖假消,既有共同的部分柠并,也有互相獨(dú)立的部分。
Spring MVC的特點(diǎn)就是富拗,它是命令式編程臼予,代碼非常容易寫(xiě),也好理解和調(diào)試啃沪。但是它是同步的粘拾,會(huì)有人覺(jué)得它性能不好。
但是我要說(shuō)的是创千,響應(yīng)式和非阻塞通常來(lái)講也不會(huì)使應(yīng)用運(yùn)行的更快缰雇。相反,非阻塞方式要求做更多的事情追驴,而且還會(huì)稍微增加一些必要的處理時(shí)間械哟。也就是說(shuō),還可能稍稍變慢一點(diǎn)氯檐,what戒良,那為啥還要用它呢体捏?
響應(yīng)式和非阻塞的關(guān)鍵好處是冠摄,在使用很少固定數(shù)目的線程和較少的內(nèi)存情況下的擴(kuò)展能力。
這使應(yīng)用在負(fù)載下更有適應(yīng)能力几缭,因?yàn)樗鼈円砸粋€(gè)更加具有可預(yù)見(jiàn)性的方式在擴(kuò)展河泳。
為了能夠觀察到這些好處,你需要有一些延遲才行年栓,比如一個(gè)既不可靠且速度又慢的網(wǎng)絡(luò)I/O拆挥,這才是響應(yīng)式開(kāi)始展示它強(qiáng)勁的地方,帶來(lái)的差異(驚喜)可能是巨大的哦某抓。
其實(shí)技術(shù)無(wú)好壞纸兔,各有各的適用場(chǎng)景罷了。
歡迎工作一到五年的Java工程師朋友們加入Java程序員開(kāi)發(fā): 854393687
群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用否副、高并發(fā)汉矿、高性能及分布式、Jvm性能調(diào)優(yōu)备禀、Spring源碼洲拇,MyBatis奈揍,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來(lái)學(xué)習(xí)提升自己,不要再用"沒(méi)有時(shí)間“來(lái)掩飾自己思想上的懶惰赋续!趁年輕男翰,使勁拼,給未來(lái)的自己一個(gè)交代纽乱!