求求你,下次面試別再問我什么是 Spring AOP 和代理了烟勋!

我們知道规求,Spring 中 AOP 是一大核心技術(shù),也是面試中經(jīng)常會(huì)被問到的問題卵惦,最近我在網(wǎng)上也看到很多面試題阻肿,其中和 Spring AOP 相關(guān)的就有不少,這篇文章主要來總結(jié)下相關(guān)的技術(shù)點(diǎn)沮尿,希望對(duì)大家有用丛塌。

0. 幾個(gè)常見的問題

針對(duì)這一塊的東西,一般下面幾個(gè)問題面試官問的比較多:

* Spring AOP用的是哪種設(shè)計(jì)模式畜疾?

* 談?wù)勀銓?duì)代理模式的理解赴邻?

* 靜態(tài)代理和動(dòng)態(tài)代理有什么區(qū)別?

* 如何實(shí)現(xiàn)動(dòng)態(tài)代理啡捶?

* Spring AOP中用的是哪種代理技術(shù)姥敛?

如果這些問題都能回答的很流暢的話,說明對(duì)代理這一塊的基本知識(shí)有一定的了解了届慈。因?yàn)槲覀冊(cè)趯?shí)際開發(fā)中徒溪,寫業(yè)務(wù)代碼會(huì)更多,所以這一塊的東西金顿,大部分人可能知道個(gè)一二,但是如果讓他們很有條理的表達(dá)出來鲤桥,可能就不那么容易了揍拆。

1. 什么是 Spring AOP?

一般面試官問到這個(gè)問題茶凳,面試者基本上都會(huì)回答:AOP 就是面向切面編程嫂拴。其實(shí)這真的是句廢話播揪,這么回答真的沒有任何意義。

或許你可以給面試官舉個(gè)例子:歌星都有好多助理筒狠,歌星最重要的一件事就是唱歌猪狈,其他事他不用關(guān)注,比如唱歌前可能需要和其他人談合作辩恼,還要布置場(chǎng)地雇庙,唱歌后還要收錢等等,這些統(tǒng)統(tǒng)交給他對(duì)應(yīng)的助理去做灶伊。也許哪一天疆前,這個(gè)歌星做慈善,免費(fèi)唱歌了聘萨,不收錢了竹椒,那么就可以把收錢這個(gè)助力給辭退了。這就是 AOP米辐,每個(gè)人各司其職胸完,靈活組合,達(dá)到一種可配置的翘贮、可插拔的程序結(jié)構(gòu)赊窥。AOP 的實(shí)現(xiàn)原理就是代理模式。

在程序中也是如此择膝,通過代理誓琼,可以詳細(xì)控制訪問某個(gè)或者某類對(duì)象的方法,在調(diào)用這個(gè)方法前做前置處理肴捉,調(diào)用這個(gè)方法后做后置處理腹侣。

2. 什么是代理模式?

代理模式的核心作用就是通過代理齿穗,控制對(duì)對(duì)象的訪問傲隶。它的設(shè)計(jì)思路是:定義一個(gè)抽象角色,讓代理角色和真實(shí)角色分別去實(shí)現(xiàn)它窃页。

真實(shí)角色:實(shí)現(xiàn)抽象角色跺株,定義真實(shí)角色所要實(shí)現(xiàn)的業(yè)務(wù)邏輯,供代理角色調(diào)用脖卖。它只關(guān)注真正的業(yè)務(wù)邏輯乒省,比如歌星唱歌。

代理角色:實(shí)現(xiàn)抽象角色畦木,是真實(shí)角色的代理袖扛,通過真實(shí)角色的業(yè)務(wù)邏輯方法來實(shí)現(xiàn)抽象方法,并在前后可以附加自己的操作,比如談合同蛆封,布置場(chǎng)地唇礁,收錢等等。

這就是代理模式的設(shè)計(jì)思路惨篱。代理模式分為靜態(tài)代理和動(dòng)態(tài)代理盏筐。靜態(tài)代理是我們自己創(chuàng)建一個(gè)代理類,而動(dòng)態(tài)代理是程序自動(dòng)幫我們生成一個(gè)代理砸讳,我們就不用管了琢融。下面我詳細(xì)介紹一下這兩種代理模式。

3. 靜態(tài)代理模式

就舉明星唱歌這個(gè)例子绣夺,根據(jù)上面提供的設(shè)計(jì)思路吏奸,首先我們需要?jiǎng)?chuàng)建明星這個(gè)抽象角色,

/**

* 明星接口類

*?@author?shengwu ni

*?@date?2018-12-07

*/

public?interface?Star?{

/**

* 唱歌方法

*/

void?sing();

}

靜態(tài)代理需要?jiǎng)?chuàng)建真實(shí)角色和代理角色陶耍,分別實(shí)現(xiàn)唱歌這個(gè)接口奋蔚,真實(shí)角色很簡(jiǎn)單,直接實(shí)現(xiàn)即可烈钞,因?yàn)檎鎸?shí)角色的主要任務(wù)就是唱歌泊碑。

/**

* 真實(shí)明星類

*?@author?shengwu ni

*?@date?2018-12-08

*/

public?class?RealStar?implements?Star?{

@Override

public?void?sing()?{

System.out.println("明星本人開始唱歌……");

}

}

代理類就需要做點(diǎn)工作了,我們思考一下毯欣,代理只是在明星唱歌前后做一些準(zhǔn)備和收尾的事馒过,唱歌這件事還得明星親自上陣,代理做不了酗钞。所以代理類里面是肯定要將真實(shí)的對(duì)象傳進(jìn)來腹忽。有了思路,我們將代理類寫出來砚作。

/**

* 明星的靜態(tài)代理類

*

*?@author?shengwu ni

*?@date?2018-12-08

*/

public?class?ProxyStar?implements?Star?{

/**

* 接收真實(shí)的明星對(duì)象

*/

private?Star star;

/**

* 通過構(gòu)造方法傳進(jìn)來真實(shí)的明星對(duì)象

*?@param?star star

*/

public?ProxyStar(Star star)?{

this.star = star;

}

@Override

public?void?sing()?{

System.out.println("代理先進(jìn)行談判……");

// 唱歌只能明星自己唱

this.star.sing();

System.out.println("演出完代理去收錢……");

}

}

這樣的話窘奏,邏輯就非常清晰了。在代理類中葫录,可以看到着裹,維護(hù)了一個(gè)Star對(duì)象,通過構(gòu)造方法傳進(jìn)來一個(gè)真實(shí)的Star對(duì)象給其賦值米同,然后在唱歌這個(gè)方法里骇扇,使用真實(shí)對(duì)象來唱歌。所以說面談面粮、收錢都是由代理對(duì)象來實(shí)現(xiàn)的少孝,唱歌是代理對(duì)象讓真實(shí)對(duì)象來做。下面寫個(gè)客戶端測(cè)試下熬苍。

/**

* 測(cè)試客戶端

*?@author?shengwu ni

*?@date?2018-12-08

*/

public?class?Client?{

/**

* 測(cè)試靜態(tài)代理結(jié)果

*?@param?args args

*/

public?static?void?main(String[] args)?{

Star realStar =?new?RealStar();

Star proxy =?new?ProxyStar(realStar);

proxy.sing();

}

}

讀者可以自己運(yùn)行下結(jié)果韭山,靜態(tài)代理比較簡(jiǎn)單。動(dòng)態(tài)代理比靜態(tài)代理使用的更廣泛冷溃,動(dòng)態(tài)代理在本質(zhì)上钱磅,代理類不用我們來管,我們完全交給工具去生成代理類即可似枕。動(dòng)態(tài)代理一般有兩種方式:JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理盖淡。

4. JDK 動(dòng)態(tài)代理

既然動(dòng)態(tài)代理不需要我們?nèi)?chuàng)建代理類,那我們只需要編寫一個(gè)動(dòng)態(tài)處理器就可以了凿歼。真正的代理對(duì)象由 JDK 在運(yùn)行時(shí)為我們動(dòng)態(tài)的來創(chuàng)建褪迟。

/**

* 動(dòng)態(tài)代理處理類

*

* @author shengwu ni

* @date 2018-12-08

*/

public?class?JdkProxyHandler?{

/**

* 用來接收真實(shí)明星對(duì)象

*/

private?Object realStar;

/**

* 通過構(gòu)造方法傳進(jìn)來真實(shí)的明星對(duì)象

*

* @param star star

*/

public?JdkProxyHandler(Star star)?{

super();

this.realStar = star;

}

/**

* 給真實(shí)對(duì)象生成一個(gè)代理對(duì)象實(shí)例

*

* @return Object

*/

public?Object?getProxyInstance()?{

return?Proxy.newProxyInstance(realStar.getClass().getClassLoader(),

realStar.getClass().getInterfaces(), (proxy, method, args) -> {

System.out.println("代理先進(jìn)行談判……");

// 唱歌需要明星自己來唱

Object?object?= method.invoke(realStar, args);

System.out.println("演出完代理去收錢……");

return?object;

});

}

}

這里說一下?Proxy.newProxyInstance() 方法,該方法接收三個(gè)參數(shù):第一個(gè)參數(shù)指定當(dāng)前目標(biāo)對(duì)象使用的類加載器,獲取加載器的方法是固定的答憔;第二個(gè)參數(shù)指定目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型味赃;第三個(gè)參數(shù)指定動(dòng)態(tài)處理器,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事件處理器的方法虐拓。網(wǎng)上針對(duì)第三個(gè)參數(shù)的寫法都是 new 一個(gè)匿名類來處理心俗,我這直接用的 Java8 里面的 lamda 表達(dá)式來寫的,都一樣蓉驹。底層原理使用的是反射機(jī)制城榛。接下來寫一個(gè)客戶端程序來測(cè)試下。

/**

* 測(cè)試客戶端

*?@author?shengwu ni

*?@date?2018-12-08

*/

public?class?Client?{

/**

* 測(cè)試JDK動(dòng)態(tài)代理結(jié)果

*?@param?args args

*/

public?static?void?main(String[] args)?{

Star realStar =?new?RealStar();

// 創(chuàng)建一個(gè)代理對(duì)象實(shí)例

Star proxy = (Star)?new?JdkProxyHandler(realStar).getProxyInstance();

proxy.sing();

}

}

可以看出态兴,創(chuàng)建一個(gè)真實(shí)的對(duì)象狠持,送給 JdkProxyHandler 就可以創(chuàng)建一個(gè)代理對(duì)象了。

我們對(duì) JDK 動(dòng)態(tài)代理做一個(gè)簡(jiǎn)單的總結(jié):相對(duì)于靜態(tài)代理瞻润,JDK 動(dòng)態(tài)代理大大減少了我們的開發(fā)任務(wù)喘垂,同時(shí)減少了對(duì)業(yè)務(wù)接口的依賴,降低了耦合度绍撞。JDK?動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類正勒,在調(diào)用具體方法前調(diào)用InvokeHandler 來處理。但是 JDK 動(dòng)態(tài)代理有個(gè)缺憾楚午,或者說特點(diǎn):JDK 實(shí)現(xiàn)動(dòng)態(tài)代理需要實(shí)現(xiàn)類通過接口定義業(yè)務(wù)方法昭齐。也就是說它始終無法擺脫僅支持 interface 代理的桎梏,因?yàn)樗脑O(shè)計(jì)就注定了這個(gè)遺憾矾柜。

5. CGLIB 動(dòng)態(tài)代理

由上面的分析可知阱驾,JDK 實(shí)現(xiàn)動(dòng)態(tài)代理需要實(shí)現(xiàn)類通過接口定義業(yè)務(wù)方法,那對(duì)于沒有接口的類怪蔑,如何實(shí)現(xiàn)動(dòng)態(tài)代理呢里覆,這就需要 CGLIB 了。

CGLIB 采用了非常底層的字節(jié)碼技術(shù)缆瓣,其原理是通過字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建子類喧枷,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢(shì)織入橫切邏輯。但因?yàn)椴捎玫氖抢^承隧甚,所以不能對(duì)final修飾的類進(jìn)行代理车荔。我們來寫一個(gè) CBLIB 代理類。

/**

* cglib代理處理類

*?@author?shengwu ni

*?@date?2018-12-08

*/

public?class?CglibProxyHandler?implements?MethodInterceptor?{

/**

* 維護(hù)目標(biāo)對(duì)象

*/

private?Object target;

public?Object?getProxyInstance(final?Object target)?{

this.target = target;

// Enhancer類是CGLIB中的一個(gè)字節(jié)碼增強(qiáng)器戚扳,它可以方便的對(duì)你想要處理的類進(jìn)行擴(kuò)展

Enhancer enhancer =?new?Enhancer();

// 將被代理的對(duì)象設(shè)置成父類

enhancer.setSuperclass(this.target.getClass());

// 回調(diào)方法忧便,設(shè)置攔截器

enhancer.setCallback(this);

// 動(dòng)態(tài)創(chuàng)建一個(gè)代理類

return?enhancer.create();

}

@Override

public?Object?intercept(Object object, Method method, Object[] args,

MethodProxy methodProxy)

?throws?Throwable?{

System.out.println("代理先進(jìn)行談判……");

// 唱歌需要明星自己來唱

Object result = methodProxy.invokeSuper(object, args);

System.out.println("演出完代理去收錢……");

return?result;

}

}

使用 CGLIB 需要實(shí)現(xiàn) MethodInterceptor?接口,并重寫intercept 方法帽借,在該方法中對(duì)原始要執(zhí)行的方法前后做增強(qiáng)處理珠增。該類的代理對(duì)象可以使用代碼中的字節(jié)碼增強(qiáng)器來獲取。接下來寫個(gè)客戶端測(cè)試程序砍艾。

/**

* 測(cè)試客戶端

*?@author?shengwu ni

*?@date?2018-12-08

*/

public?class?Client?{

/**

* 測(cè)試Cglib動(dòng)態(tài)代理結(jié)果

*?@param?args args

*/

public?static?void?main(String[] args)?{

Star realStar =?new?RealStar();

Star proxy = (Star)?new?CglibProxyHandler().getProxyInstance(realStar);

proxy.sing();

}

}

這個(gè)客戶端測(cè)試程序和 JDK 動(dòng)態(tài)代理的邏輯一模一樣蒂教,所以也可以看出,代理模式中的動(dòng)態(tài)代理脆荷,其實(shí)套路都是相同的凝垛,只是使用了不同的技術(shù)而已。

我們也對(duì) CGLIB 動(dòng)態(tài)代理做一下總結(jié):CGLIB 創(chuàng)建的動(dòng)態(tài)代理對(duì)象比 JDK 創(chuàng)建的動(dòng)態(tài)代理對(duì)象的性能更高简烘,但是 CGLIB 創(chuàng)建代理對(duì)象時(shí)所花費(fèi)的時(shí)間卻比 JDK 多得多苔严。所以對(duì)于單例的對(duì)象,因?yàn)闊o需頻繁創(chuàng)建對(duì)象孤澎,用 CGLIB 合適届氢,反之使用JDK方式要更為合適一些。同時(shí)由于 CGLIB 由于是采用動(dòng)態(tài)創(chuàng)建子類的方法覆旭,對(duì)于final修飾的方法無法進(jìn)行代理退子。

當(dāng)然了,不管是哪種動(dòng)態(tài)代理技術(shù)型将,在上面的代碼里寂祥,要代理的類中可能不止一種方法,有時(shí)候我們需要對(duì)特定的方法進(jìn)行增強(qiáng)處理七兜,所以可以對(duì)傳入的 method 參數(shù)進(jìn)行方法名的判斷丸凭,再做相應(yīng)的處理。

6. Spring AOP 采用哪種代理腕铸?

JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理均是實(shí)現(xiàn) Spring AOP 的基礎(chǔ)惜犀。對(duì)于這一塊內(nèi)容,面試官問的比較多狠裹,他們往往更想聽聽面試者是怎么回答的虽界,有沒有看過這一塊的源碼等等。

針對(duì)于這一塊內(nèi)容涛菠,我們看一下 Spring 5 中對(duì)應(yīng)的源碼是怎么說的莉御。

public?class?DefaultAopProxyFactory?implements?AopProxyFactory,?Serializable?{

@Override

public?AopProxy?createAopProxy(AdvisedSupport config)?throws?AopConfigException?{

if?(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {

Class targetClass = config.getTargetClass();

if?(targetClass ==?null) {

throw?new?AopConfigException("TargetSource cannot determine target class: "?+

"Either an interface or a target is required for proxy creation.");

}

// 判斷目標(biāo)類是否是接口或者目標(biāo)類是否Proxy類型撇吞,若是則使用JDK動(dòng)態(tài)代理

if?(targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {

return?new?JdkDynamicAopProxy(config);

}

// 配置了使用CGLIB進(jìn)行動(dòng)態(tài)代理或者目標(biāo)類沒有接口,那么使用CGLIB的方式創(chuàng)建代理對(duì)象

return?new?ObjenesisCglibAopProxy(config);

}

else?{

// 上面的三個(gè)方法沒有一個(gè)為true礁叔,那使用JDK的提供的代理方式生成代理對(duì)象

return?new?JdkDynamicAopProxy(config);

}

}

//其他方法略……

}

從上述源碼片段可以看出牍颈,是否使用 CGLIB 是在代碼中進(jìn)行判斷的,判斷條件是 config.isOptimize()晴圾、config.isProxyTargetClass() 和?hasNoUserSuppliedProxyInterfaces(config)颂砸。

其中,config.isOptimize() 與 config.isProxyTargetClass()默認(rèn)返回都是 false死姚,這種情況下判斷結(jié)果就由

hasNoUserSuppliedProxyInterfaces(config)的結(jié)果決定了。

簡(jiǎn)單來說勤篮,

hasNoUserSuppliedProxyInterfaces(config) 就是在判斷代理的對(duì)象是否有實(shí)現(xiàn)接口都毒,有實(shí)現(xiàn)接口的話直接走 JDK 分支,即使用 JDK 的動(dòng)態(tài)代理碰缔。

所以基本上可以總結(jié)出 Spring AOP 中的代理使用邏輯了:如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口账劲,默認(rèn)情況下會(huì)采用 JDK 的動(dòng)態(tài)代理實(shí)現(xiàn) AOP;如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)了接口金抡,則采用 CGLIB 庫瀑焦,Spring 會(huì)自動(dòng)在 JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理之間轉(zhuǎn)換。

當(dāng)然梗肝,源碼我也沒讀那么深榛瓮,暫且就只能寫到這,后面深入了巫击,有新的見解再給大家分享禀晓。還記得文章開頭的幾個(gè)問題嗎?相信你讀到這里坝锰,心中應(yīng)該已經(jīng)有了答案了粹懒。

如果覺得對(duì)自己有幫助,可以轉(zhuǎn)發(fā)給更多的伙伴們顷级。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凫乖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弓颈,更是在濱河造成了極大的恐慌帽芽,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恨豁,死亡現(xiàn)場(chǎng)離奇詭異嚣镜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)橘蜜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門菊匿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來付呕,“玉大人,你說我怎么就攤上這事跌捆』罩埃” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵佩厚,是天一觀的道長(zhǎng)姆钉。 經(jīng)常有香客問我,道長(zhǎng)抄瓦,這世上最難降的妖魔是什么潮瓶? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮钙姊,結(jié)果婚禮上毯辅,老公的妹妹穿的比我還像新娘。我一直安慰自己煞额,他們只是感情好思恐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膊毁,像睡著了一般胀莹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上婚温,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天描焰,我揣著相機(jī)與錄音,去河邊找鬼缭召。 笑死栈顷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嵌巷。 我是一名探鬼主播萄凤,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼搪哪!你這毒婦竟也來了靡努?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤晓折,失蹤者是張志新(化名)和其女友劉穎惑朦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漓概,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漾月,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胃珍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梁肿。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜓陌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吩蔑,到底是詐尸還是另有隱情钮热,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布烛芬,位于F島的核電站隧期,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赘娄。R本人自食惡果不足惜仆潮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望擅憔。 院中可真熱鬧鸵闪,春花似錦、人聲如沸暑诸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽个榕。三九已至,卻和暖如春芥喇,著一層夾襖步出監(jiān)牢的瞬間西采,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工继控, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留械馆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓武通,卻偏偏與公主長(zhǎng)得像霹崎,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冶忱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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