Spring爸爸又給Spring MVC生了個(gè)弟弟叫Spring WebFlux

情景引入

很早之前埂息,Java就火起來了皮璧,是因?yàn)樗朴陂_發(fā)和處理網(wǎng)絡(luò)方面的應(yīng)用舟扎。

Java有一個(gè)愛好,就是喜歡制定規(guī)范標(biāo)準(zhǔn)悴务,但自己又不善于去實(shí)現(xiàn)睹限。

反倒是一些服務(wù)提供商使用它的規(guī)范標(biāo)準(zhǔn)來制造應(yīng)用服務(wù)器而賺的盆滿缽滿。

企業(yè)用戶因要使用這些應(yīng)用服務(wù)器而向提供商支付高額費(fèi)用讯檐,而且也不是特別好用邦泄。

一個(gè)青年才俊為了打破這種局面而奔走呼號(hào)、奮發(fā)圖強(qiáng)裂垦。

自我介紹

顯然顺囊,這個(gè)青年才俊就是后來的Spring。

因企業(yè)應(yīng)用大都和web相關(guān)蕉拢,而Java的web標(biāo)準(zhǔn)中較核心的一部分其實(shí)就是JavaEE里的Servlet特碳。

Spring和Servlet“相親相愛”一番后,我就來到了這個(gè)世界晕换。我的全名叫Spring MVC午乓,這里的Spring既是我的姓也是我的“爸爸”,那Servlet就是我的“媽媽”了闸准,大家叫我MVC就行了益愈。

那個(gè)年代社會(huì)很落后,條件也不好夷家,好歹我們要求也不高蒸其,求個(gè)溫飽就行了。

所以我的媽媽Servlet和她的閨蜜Filter天生就是同步阻塞的库快,包括她們同事HttpServletRequest的getParameter摸袁,getPart等這些方法也都是阻塞的。

雖然我的爸爸Spring給了我23條染色體來進(jìn)行改良义屏,但不要忘了我還從Servlet媽媽那里繼承了23條靠汁,所以我也是同步阻塞的。不過我的“長(zhǎng)相”已經(jīng)好看很多了闽铐,因?yàn)镾pring爸爸知道蝶怔,在以后的日子里,除了拼實(shí)力之外兄墅,顏值也是非常重要的踢星。

因?yàn)槲覌寢孲ervlet是一個(gè)規(guī)范,我爸爸Spring是一個(gè)框架察迟,所以我跟他們一樣斩狱,都是無法自己獨(dú)立運(yùn)行的。

所以在我們要運(yùn)行的時(shí)候扎瓶,必須要尋找一個(gè)特殊的“家”所踊,通常稱它為Servlet容器,比如tomcat就算非常知名的一個(gè)概荷。

Servlet容器熟知我極有可能阻塞當(dāng)前執(zhí)行線程秕岛,所以專門量身打造。它給我準(zhǔn)備了一個(gè)非常大的線程池误证,里面有好多線程继薛。每過來一個(gè)請(qǐng)求,它就扔給我一個(gè)線程愈捅,說自己玩去吧遏考,隨便“折騰”。

好在那時(shí)美國(guó)那個(gè)叫喬布斯的家伙被自己的公司趕出去在外面“流浪”蓝谨,Servlet容器為我量身打造的這種方法完全能夠勝任日常灌具,關(guān)鍵還非常的簡(jiǎn)單。

這種小富即安的日子就這樣往前過著譬巫。

兄弟出生

生命不息咖楣,變化不止。隨著喬布斯推出iphone芦昔,智能機(jī)瞬間大火诱贿,全民進(jìn)入移動(dòng)互聯(lián)網(wǎng)時(shí)代。激增的網(wǎng)民數(shù)量咕缎,給現(xiàn)有軟件架構(gòu)帶來極大的挑戰(zhàn)珠十。

一般來說,社會(huì)越發(fā)達(dá)凭豪,分工越精細(xì)宵睦,對(duì)單一工種的要求就越高。

軟件也是如此墅诡,在傳統(tǒng)“大塊頭”軟件表現(xiàn)的越來越格格不入的時(shí)候壳嚎,微服務(wù)就如一絲春風(fēng)吹了進(jìn)來。

按它的指導(dǎo)原則末早,將大軟件按某種方式拆分為一個(gè)個(gè)小工程烟馅。小工程規(guī)模小,便于管理然磷,而且機(jī)動(dòng)性也好郑趁,功能聚合性更好。它承受的并發(fā)應(yīng)該更高姿搜。

有人覺得與微服務(wù)比起來寡润,過去使用的web服務(wù)器如tomcat略顯笨重捆憎,不夠輕量級(jí)。也有人說tomcat內(nèi)部一個(gè)請(qǐng)求一個(gè)線程這種阻塞執(zhí)行方式消耗太多線程梭纹,不太容易支撐超高并發(fā)躲惰。

無論怎么說,簡(jiǎn)而言之一句話变抽,一個(gè)全新的時(shí)代已經(jīng)到來础拨。

此時(shí)我們需要一個(gè)更加輕量級(jí)web應(yīng)用,它使用更少的硬件資源和線程绍载,反而更容易處理高并發(fā)诡宗。那么它一定是異步非阻塞的。

這樣的使命自然落到了響應(yīng)式編程的范疇上了击儡。所以我的爸爸Spring審時(shí)度勢(shì)塔沃,在5.0之后就趕緊把我推出來了。

沒錯(cuò)阳谍,我就是Spring WebFlux芳悲,這里的Spring既是我的姓也是我爸爸。大家可以叫我WebFlux边坤。初來乍到名扛,好多人都對(duì)我不熟悉,請(qǐng)容許我介紹一番茧痒。

首先這個(gè)響應(yīng)式究竟是什么意思呢肮韧?響應(yīng)式這個(gè)術(shù)語,指的是一個(gè)編程模型旺订,它是圍繞著對(duì)變化的反映來構(gòu)建的弄企。

如網(wǎng)絡(luò)組件用來響應(yīng)I/O事件,UI控制器用來響應(yīng)鼠標(biāo)事件等等区拳。按照這種意識(shí)的話拘领,非阻塞就是響應(yīng)式的,對(duì)操作完成或數(shù)據(jù)可用通知事件的響應(yīng)方式樱调。

另外一個(gè)關(guān)于響應(yīng)式的機(jī)制是非阻塞后壓约素。在命令式代碼中,同步阻塞調(diào)用帶有自然的后壓迫使調(diào)用者等待笆凌。

在異步代碼中圣猎,它變得非常重要,用來控制事件的速率乞而,以至于不讓一個(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)來處理請(qǐng)求。典型地眉撵,線程數(shù)與CPU的核數(shù)相同侦香。

這里還要感謝我的姥爺Java 8落塑,他老人家引入了lambda表達(dá)式造就了函數(shù)式編程API纽疟。這對(duì)于非阻塞應(yīng)用和連續(xù)式API來說是一個(gè)非常棒的東西,允許以聲明的方式把異步邏輯組合起來憾赁。

我感覺我的爸爸Spring已經(jīng)超越了一個(gè)框架污朽,成為一個(gè)平臺(tái)了。所以他自己并沒有親自去實(shí)現(xiàn)響應(yīng)式處理龙考,而是為我選擇Reactor作為響應(yīng)式庫(kù)蟆肆。

Reactor提供Flux和Mono類型,擁有豐富的操作符晦款,支持非阻塞后壓炎功,使用函數(shù)式API來組合異步邏輯。并且Reactor強(qiáng)烈聚焦于Java服務(wù)器端缓溅。它在開發(fā)時(shí)就已經(jīng)與爸爸Spring親密協(xié)作了蛇损。

爸爸說,我也支持其它的庫(kù)如RxJava坛怪,但看樣子似乎讓我更愛Reactor一些淤齐。

這就是我,WebFlux袜匿,一個(gè)集天時(shí)地利于一身的幸運(yùn)兒更啄。但你是不是已經(jīng)暈暈的啦,沒關(guān)系居灯,慢慢來祭务。

包羅萬象

我想,大家都看出了我爸爸Spring的野心怪嫌,他不僅要成為一個(gè)平臺(tái)待牵,還要建起自己的生態(tài)系統(tǒng),豎起壁壘喇勋。

所以他的核心事業(yè)就是進(jìn)行抽象缨该,組合和裝配,進(jìn)而包羅萬象川背。說的掉渣一些贰拿,就是哪個(gè)技術(shù)好蛤袒,就給它整合進(jìn)來。

為了抹平底層不同web服務(wù)器的差異膨更,我爸爸抽象了一個(gè)最低級(jí)別的契約接口妙真,HttpHandler,用于響應(yīng)式HTTP請(qǐng)求的處理荚守。

Mono 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)來的。

顯而易見蛔垢,HttpHandler的目標(biāo)是抽象出來對(duì)不同HTTP服務(wù)器的使用击孩,說白了就是為了和底層服務(wù)器對(duì)接。但由于太偏底層鹏漆,不利用上層代碼使用巩梢。

為此,我的爸爸又抽象出一個(gè)稍微高一點(diǎn)級(jí)別的契約接口甫男,WebHandler且改,用于Web請(qǐng)求處理。很明顯板驳,WebHandler的目標(biāo)是提供web應(yīng)用中廣泛使用的通用特性又跛,如Session、表單數(shù)據(jù)和附件等等若治,也是為了更容易和上層代碼對(duì)接慨蓝。

很自然的,WebHandler是構(gòu)建于HttpHandler之上的端幼,換句話說WebHander的處理會(huì)通過一個(gè)適配器HttpWebHandlerAdapter最終代理給HttpHandler來執(zhí)行礼烈。

WebHandler接口也只有一個(gè)方法:

Mono 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)求的處理過程是一個(gè)鏈?zhǔn)降模詈蟛攀且粋€(gè)WebHandler阴汇,它前面可以插入多個(gè)錯(cuò)誤處理器数冬,WebExceptionHandler,多個(gè)過濾器搀庶,WebFilter拐纱。

這是錯(cuò)誤處理器接口:

Mono handle(ServerWebExchange exchange, java.lang.Throwable ex);

這是過濾器接口:

Mono filter(ServerWebExchange exchange, WebFilterChain chain);

可見,我的爸爸Spring的抽象能力非常強(qiáng)哥倔,對(duì)下抽象一個(gè)接口秸架,抹平了不同服務(wù)器的差異。對(duì)上抽象一個(gè)接口未斑,可以用于支撐不同的編程模型咕宿。

都有哪些編程模型呢币绩,請(qǐng)繼續(xù)往下看吧蜡秽。

皮囊之下

上面我在介紹自己的時(shí)候使用了美顏,所以諸位很難看清我的“真面目”缆镣,下面就來進(jìn)行一下自我剖析芽突,看看真實(shí)的我。

我包含一個(gè)輕量級(jí)函數(shù)式編程模型董瞻,函數(shù)被用來參與處理請(qǐng)求寞蚌,它是相對(duì)于基于注解編程模型的另一種選擇,這種編程模型叫做函數(shù)式端點(diǎn)钠糊,functional endpoints挟秤,是構(gòu)建于上面提到的WebHandler之上的。

我是使用HandlerFunction來處理一個(gè)HTTP請(qǐng)求的抄伍,這是一個(gè)函數(shù)式接口艘刚,也稱處理函數(shù):

@FunctionalInterface

public interface HandlerFunction {

reactor.core.publisher.Mono 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)求過來后钱床,該由哪個(gè)處理函數(shù)去處理呢荚斯?

這自然要用到我的另一個(gè)函數(shù)式接口RouterFunction來搞定,稱為路由函數(shù):

@FunctionalInterface

public interface RouterFunction {

reactor.core.publisher.Mono> 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ù)撞叨,還有行為金踪。

下面通過一些示例,來更加直觀的幫助大家認(rèn)識(shí)這兩個(gè)函數(shù)式接口牵敷。

因處理函數(shù)是函數(shù)式接口胡岔,所以可以直接用一個(gè)lambda表達(dá)式來處理請(qǐng)求,如下:

HandlerFunction handler = request -> Response.ok().body("Hello World");

這就表示當(dāng)任何一個(gè)請(qǐng)求過來時(shí)枷餐,都返回Hello World作為響應(yīng)靶瘸。

在實(shí)際應(yīng)用中,處理邏輯一般都很復(fù)雜毛肋,肯定不是一個(gè)lambda表達(dá)式能搞定的怨咪,此時(shí)希望把處理方法專門寫到一個(gè)類里,就叫處理器類润匙,和MVC里的Controller差不多一回事诗眨。

下面就是一個(gè)Person的處理器類:

public class PersonHandler {

public Mono listPeople(ServerRequest request) {

// ...

}

public Mono createPerson(ServerRequest request) {

// ...

}

public Mono getPerson(ServerRequest request) {

// ...

}

}

此時(shí)就可以通過處理函數(shù),引用這些處理器方法了孕讳,如下:

PersonHandler handler = new PersonHandler();

HandlerFunction list = handler::listPeople;

HandlerFunction create = handler::createPerson;

HandlerFunction get = handler::getPerson;

要想使請(qǐng)求能夠正確被路由匠楚,首先要定義好路由函數(shù),如下:

RouterFunction 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方法處理帜平。

可見幽告,一個(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ì)我非常溺愛吧蚁署,告訴你,確實(shí)是這樣的蚂四。不過考慮到大家伙一路走來對(duì)Spring的不離不棄光戈,爸爸也設(shè)身處地為你們著想。

為此,我除了支持函數(shù)式端點(diǎn)這種編程模型之外,還支持一種編程模型叫基于注解的控制器,annotated controllers,沒錯(cuò)氢烘,就是MVC里的那個(gè)。

話說的再白一些愚屁,就是大家已經(jīng)非常熟悉的Spring MVC那套東西死宣,我百分之百的完全支持,妥妥的惋啃,放心使用哼鬓。

但是,并不是所有的控制器方法參數(shù)都支持響應(yīng)式類型边灭,只有一些支持异希,如WebSession,java.security.Principal绒瘦,@RequestBody称簿,HttpEntity<B>,@RequestPart等惰帽。

下面看一個(gè)示例:

@PostMapping("/")

public String handle(@RequestBody Mono> parts) {

// ...

}

@PostMapping("/")

public String handle(@RequestBody Flux parts) {

// ...

}

@PostMapping("/accounts")

public void handle(@RequestBody Mono account) {

// ...

}

不過對(duì)于控制器方法的所有返回值憨降,都是支持響應(yīng)式類型的。

各有千秋

Spring MVC和Spring WebFlux可以一起使用该酗,從設(shè)計(jì)上講授药,它們互為繼續(xù)士嚎、互為一致。

它們的關(guān)系悔叽,請(qǐng)看下圖莱衩,既有共同的部分,也有互相獨(dú)立的部分娇澎。

Spring MVC的特點(diǎn)就是笨蚁,它是命令式編程,代碼非常容易寫趟庄,也好理解和調(diào)試赚窃。但是它是同步的,會(huì)有人覺得它性能不好岔激。

但是我要說的是勒极,響應(yīng)式和非阻塞通常來講也不會(huì)使應(yīng)用運(yùn)行的更快。相反虑鼎,非阻塞方式要求做更多的事情辱匿,而且還會(huì)稍微增加一些必要的處理時(shí)間。也就是說炫彩,還可能稍稍變慢一點(diǎn)匾七,what,那為啥還要用它呢江兢?

響應(yīng)式和非阻塞的關(guān)鍵好處是昨忆,在使用很少固定數(shù)目的線程和較少的內(nèi)存情況下的擴(kuò)展能力。

這使應(yīng)用在負(fù)載下更有適應(yīng)能力杉允,因?yàn)樗鼈円砸粋€(gè)更加具有可預(yù)見性的方式在擴(kuò)展邑贴。

為了能夠觀察到這些好處,你需要有一些延遲才行叔磷,比如一個(gè)既不可靠且速度又慢的網(wǎng)絡(luò)I/O拢驾,這才是響應(yīng)式開始展示它強(qiáng)勁的地方,帶來的差異(驚喜)可能是巨大的哦改基。

其實(shí)技術(shù)無好壞繁疤,各有各的適用場(chǎng)景罷了。

本文轉(zhuǎn)載自微信公眾號(hào):編程新說

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秕狰,一起剝皮案震驚了整個(gè)濱河市稠腊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸣哀,老刑警劉巖架忌,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異诺舔,居然都是意外死亡鳖昌,警方通過查閱死者的電腦和手機(jī)备畦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來许昨,“玉大人懂盐,你說我怎么就攤上這事「獾担” “怎么了莉恼?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)速那。 經(jīng)常有香客問我俐银,道長(zhǎng),這世上最難降的妖魔是什么端仰? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任捶惜,我火速辦了婚禮,結(jié)果婚禮上荔烧,老公的妹妹穿的比我還像新娘吱七。我一直安慰自己,他們只是感情好鹤竭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布踊餐。 她就那樣靜靜地躺著,像睡著了一般臀稚。 火紅的嫁衣襯著肌膚如雪吝岭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天吧寺,我揣著相機(jī)與錄音窜管,去河邊找鬼。 笑死撮执,一個(gè)胖子當(dāng)著我的面吹牛微峰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抒钱,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼颜凯!你這毒婦竟也來了谋币?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤症概,失蹤者是張志新(化名)和其女友劉穎蕾额,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彼城,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诅蝶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年退个,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片调炬。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡语盈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缰泡,到底是詐尸還是另有隱情刀荒,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布棘钞,位于F島的核電站缠借,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宜猜。R本人自食惡果不足惜泼返,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姨拥。 院中可真熱鬧符隙,春花似錦、人聲如沸垫毙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)综芥。三九已至丽蝎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膀藐,已是汗流浹背屠阻。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留额各,地道東北人国觉。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像虾啦,于是被迫代替她去往敵國(guó)和親麻诀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容