Android aop切點(diǎn)表達(dá)式(execution)

前言

本文是AOP入門(mén)系列的基礎(chǔ)文章蝠检,不會(huì)講太多很深入的東西,如果為你想學(xué)習(xí)AOP挚瘟,但是遇到了種種障礙而無(wú)法進(jìn)一步學(xué)習(xí)叹谁,那本文或許能給你提供一些幫助和思路。

我個(gè)人在學(xué)習(xí)aop的過(guò)程中也和很多新手一樣碰到過(guò)各種麻煩的問(wèn)題乘盖,比如說(shuō)在入門(mén)aop的時(shí)候焰檩,需要做aspectj相關(guān)的配置,這一步失敗了就無(wú)法繼續(xù)coding订框;比如析苫,切點(diǎn)表達(dá)式寫(xiě)錯(cuò)了,那就無(wú)法攔截原生方法做下一步的業(yè)務(wù)邏輯的操作布蔗;甚至說(shuō)對(duì)aop相關(guān)的很多概念模糊不清藤违,只知道幾個(gè)常用的相關(guān)注解@Aspect@Before纵揍、@Around,只能看著別人的demo去敲一下常用的案例卻無(wú)法在自己的項(xiàng)目中靈活的定制自己的aop议街。不過(guò)沒(méi)有關(guān)系泽谨,這個(gè)系列,我會(huì)從基礎(chǔ)---應(yīng)用---原理幫大家了解aop相關(guān)的重要知識(shí)。

推薦閱讀系列文章

Android aop Advice(通知吧雹、增強(qiáng))
Android aop(AspectJ)查看新的代理類(lèi)
Android AOP面向切面編程詳解
防止按鈕連續(xù)點(diǎn)擊

下面開(kāi)始講解aop切點(diǎn)表達(dá)式骨杂,在這之前我希望你做好2件事:

  • 1、aop是做什么的雄卷?能解決什么問(wèn)題搓蚪?為什么要用它?
  • 2丁鹉、android studio中配置好aop妒潭。推薦使用aspectjx

一、案例分析:

@Aspect
public class Test {
    final String TAG = Test.class.getSimpleName();

    @Before("execution(* *..MainActivity.on*(..))")
    public void logLifeCycle(JoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = joinPoint.getThis().getClass().getSimpleName();
        Log.e(TAG, "class:" + className+"   method:" + methodSignature.getName());
    }

    @Around("execution(* *..MainActivity.testAOP())")
    public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String key = proceedingJoinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodAroundFirst: before " + key+"do something");
        //執(zhí)行原方法
        proceedingJoinPoint.proceed();
        Log.e(TAG, "onActivityMethodAroundSecond: after " + key+"do something");
    }
}

這里寫(xiě)了2個(gè)方法揣钦,logLifeCycle()是在MainActivity的生命周期的方法中打印log日志雳灾;onActivityMethodAround是攔截MainActivity中的一個(gè)testAOP()方法,在原方法執(zhí)行執(zhí)行前冯凹、后做一些事情谎亩。

重點(diǎn)是搞清楚@Aspect@Before宇姚、@Around這幾個(gè)注解匈庭,難點(diǎn)是切點(diǎn)表達(dá)式的書(shū)寫(xiě)。"execution(* *..MainActivity.on*(..))"execution(* *..MainActivity.testAOP())就是切點(diǎn)表達(dá)式浑劳。

這個(gè)地方是非常容易犯錯(cuò)的阱持,因?yàn)楹芏嗳藢?duì)這個(gè)規(guī)則沒(méi)有搞懂,對(duì)很多概念模糊不清呀洲,所以很多新手難以自定義切點(diǎn)表達(dá)式紊选。

下面從幾個(gè)基礎(chǔ)概念出發(fā),帶你逐步掌握自定義切點(diǎn)表達(dá)式道逗。
(提示:別看見(jiàn)概念有點(diǎn)多兵罢,分類(lèi)也多就害怕了,其實(shí)不用害怕的滓窍,這里先做一個(gè)全方位的了解卖词,后面寫(xiě)表達(dá)式的時(shí)候是很簡(jiǎn)單的)

二、AOP相關(guān)的幾個(gè)基礎(chǔ)概念

  • Joinpoint(連接點(diǎn)):是指那些被攔截到的點(diǎn)吏夯,比如說(shuō)方法的調(diào)用此蜈,成員屬性的訪(fǎng)問(wèn)甚至異常的處理;
  • Pointcut(切入點(diǎn)):所謂切入點(diǎn)是指我們要對(duì)哪些 Joinpoint 進(jìn)行攔截的定義噪生。
  • Advice(通知):所謂通知是指攔截到 Joinpoint 之后所要做的事情就是通知裆赵。
  • Weaving(織入):是指把增強(qiáng)應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建新的代理對(duì)象的過(guò)程. AspectJ 采用編譯期織入和類(lèi)裝在期織入 。
  • Aspect(切面):是切入點(diǎn)和通知(引介)的結(jié)合 跺嗽。

2.1Advice(通知)

類(lèi)型 描述
Before 前置通知, 在目標(biāo)執(zhí)行之前執(zhí)行通知
After 后置通知, 目標(biāo)執(zhí)行后執(zhí)行通知
Around 環(huán)繞通知, 在目標(biāo)執(zhí)行中執(zhí)行通知, 控制目標(biāo)執(zhí)行時(shí)機(jī)
AfterReturning 后置返回通知, 目標(biāo)返回時(shí)執(zhí)行通知
AfterThrowing 異常通知, 目標(biāo)拋出異常時(shí)執(zhí)行通知

2.2切入點(diǎn)指示符

切入點(diǎn)指示符用來(lái)指示切入點(diǎn)表達(dá)式目的战授,AspectJ切入點(diǎn)指示符如下:

  • execution:用于匹配方法執(zhí)行的連接點(diǎn)

  • within:限制鏈接點(diǎn)匹配指定的類(lèi)型

  • this:用于匹配當(dāng)前AOP代理對(duì)象類(lèi)型的執(zhí)行方法页藻;注意是AOP代理對(duì)象的類(lèi)型匹配,這樣就可能包括引入接口也類(lèi)型匹配

  • target:限制鏈接點(diǎn)匹配目標(biāo)對(duì)象為指定類(lèi)型的類(lèi)

  • @within:匹配所有使用了xx注解的類(lèi)(注意是類(lèi))

  • @annotation: 匹配使用了xx注解的方法(注意是方法)

2.3切入點(diǎn)表達(dá)式語(yǔ)法

比如上面案例中的execution表達(dá)式

  execution(*  *..MainActivity.on*(..))
  • execution():切入點(diǎn)指示符植兰;
  • 第一個(gè)*:表示任意方法返回類(lèi)型份帐;
  • *..:表示省略了MainActivity的包名,當(dāng)然這里也可以寫(xiě)出完整包名楣导;
  • on*表示MainActivity中所有以on開(kāi)頭的方法废境;
  • (..):表示方法的參數(shù)可以是任意類(lèi)型且個(gè)數(shù)任意

AspectJ類(lèi)型匹配的通配符:

  1、 *:匹配任何數(shù)量字符筒繁;

  2噩凹、..:匹配任何數(shù)量字符的重復(fù),如在類(lèi)型模式中匹配任何數(shù)量子包膝晾;而在方法參數(shù)模式中匹配任何數(shù)量參數(shù)栓始。

  3、+:匹配指定類(lèi)型的子類(lèi)型血当;僅能作為后綴放在類(lèi)型模式后邊幻赚。

三、@Pointcut

3.1臊旭、@Pointcut的基本定義

這里還是以打印Activity生命周期為例落恼,先看看之前的寫(xiě)法

    @Before("execution(* *..MainActivity.on*(..))")
    public void logLifeCycle(JoinPoint joinPoint)  { }

這里直接用通知@Before+切入點(diǎn)表達(dá)式就完成了。

下面看看另一種實(shí)現(xiàn)方式
定義切點(diǎn)

  //切點(diǎn)表達(dá)式
  @Pointcut("execution(* *..MainActivity.on*(..))")   
  //切點(diǎn)簽名方法
  private void log(){}

使用定義的Pointcut

    //使用切點(diǎn)方法  
    @Before("log()")
    public void logLifeCycle(JoinPoint joinPoint)  { }

這2種方式的實(shí)現(xiàn)效果是完全一樣的离熏,但是方法2比方法1更靈活佳谦。@Pointcut是專(zhuān)門(mén)用來(lái)定義切點(diǎn)的,它讓切點(diǎn)表達(dá)式可以復(fù)用滋戳。

3.2钻蔑、@Pointcut帶參數(shù)

3.2切點(diǎn)指示符

切點(diǎn)指示符是切點(diǎn)定義的關(guān)鍵字,切點(diǎn)表達(dá)式以切點(diǎn)指示符開(kāi)始奸鸯∵湫Γ可以切點(diǎn)指示符來(lái)告訴切點(diǎn)將要匹配什么,有以下9種切點(diǎn)指示符:execution娄涩、within窗怒、thistarget蓄拣、@within扬虚、@annotation,下面一一介結(jié)這幾種切點(diǎn)指示符球恤。

提示:由于篇幅有限辜昵,下面只展示切點(diǎn)表達(dá)式,完整代碼可以在文末地址下載查看

execution

execution表達(dá)式語(yǔ)法:

execution(<修飾符模式>咽斧?<返回類(lèi)型模式><方法名模式>(<參數(shù)模式>)<異常模式>?)

注意:execution的粒度為方法路鹰,也就是說(shuō)是匹配方法的

除了返回類(lèi)型模式贷洲、方法名模式和參數(shù)模式外收厨,其它項(xiàng)都是可選的晋柱。

execution是一種使用頻率比較高的切點(diǎn)指示符,它以方法的執(zhí)行作為切入點(diǎn)诵叁。它可以說(shuō)是最重要的雁竞,其它的指示符都是輔助它的。

比如通過(guò)如下表達(dá)式可以精確地匹配到Person類(lèi)里的eat()方法

@Before("execution(* com.zx.aop1.Person.eat())")

如果要匹配Person類(lèi)里的所有方法拧额,可以使用通配符碑诉。

 @Before("execution(* com.zx.aop1.Person.*(..))")

第一個(gè)*表示返回值為任意類(lèi)型,第二個(gè)*表示這個(gè)類(lèi)里的所有方法侥锦,()括號(hào)表示參數(shù)列表进栽,括號(hào)里的用兩個(gè)點(diǎn)號(hào)表示匹配任意個(gè)參數(shù),包括0個(gè)恭垦。

within

為了方便類(lèi)型(如接口快毛、類(lèi)名、包名)過(guò)濾方法番挺,AOP 提供了within關(guān)鍵字唠帝。其語(yǔ)法格式如下:

within(<type name>)

注意:within的粒度為類(lèi)

匹配com.zx.aop1.person.Student類(lèi)中的所有方法

    @Pointcut("within(com.zx.aop1.person.Student)")

匹配com.zx.aop1.person包及其子包中所有類(lèi)中的所有方法

    @Pointcut("within(com.zx.aop1.person..*)")

匹配com.zx.aop1.person.Person類(lèi)及其子類(lèi)的所有方法

    @Pointcut("within(com.zx.aop1.person.Person+)")

匹配所有實(shí)現(xiàn)com.zx.aop1.person.Human接口的類(lèi)的所有方法,包括接口方法和實(shí)現(xiàn)類(lèi)的額外方法

    @Pointcut("within(com.zx.aop1.person.Human+)")
args

用于匹配當(dāng)前執(zhí)行的方法傳入的參數(shù) (args屬于動(dòng)態(tài)切入點(diǎn)玄柏,這種切入點(diǎn)開(kāi)銷(xiāo)非常大襟衰,非特殊情況最好不要使用)

    @Pointcut(value = "execution(* com.zx.aop1.MainActivity.testArgs(..)) && args(arg1)")
    private void pc1(String arg1){}

    /**
     * 通過(guò)args()傳參數(shù)
     * @param arg1
     */
    @Before("pc1(arg1)")
    public void testArgs1(String arg1){
       Log.e(TAG, "testArgs1--- : "+arg1 );
    }

這種獲取參數(shù)的方式不太靈活,而且開(kāi)銷(xiāo)大粪摘,所以可以用另外一種更加便捷的方式獲取參數(shù)瀑晒,可以通過(guò)joinPoint.getArgs()的方式去拿方法參數(shù)。比如上面例子徘意,可以用如下方式實(shí)現(xiàn):

 @Pointcut(value = "execution(* com.zx.aop1.MainActivity.testArgs(..))")
    private void pc2(){}

    /**
     * 通過(guò)JoinPoint連接點(diǎn)的方式去拿方法參數(shù)
     * @param joinPoint
     */
    @Before("pc2()")
    public void testArgs2(JoinPoint joinPoint){
        for (Object arg : joinPoint.getArgs()) {
            Log.e(TAG, "testArgs2---: "+arg );
        }
    }
@within

語(yǔ)法:

@within(注解類(lèi)型)

匹配所有使用了CheckWithin注解的類(lèi)(注意是類(lèi))

    @Pointcut("@within(com.zx.aop1.CheckWithin)")

只能作用于類(lèi)苔悦,不能是方法,也不能是接口映砖。

@annotation

匹配使用了CheckAop注解的方法(注意是方法)

   @Pointcut("@annotation(com.zx.aop1.CheckAop)")
    private void aAnnotation1() {
    }

 @After("aAnnotation1()")
    public void testaAnnotation(JoinPoint joinPoint) {
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        CheckAop checkAop = method.getAnnotation(CheckAop.class);
        Log.e(TAG, "testaAnnotation--: "+checkAop.value() );
    }

這里通過(guò)JoinPoint拿到對(duì)應(yīng)的方法间坐,再通過(guò)反射獲取該方法的注解的值。此外還有一種簡(jiǎn)單的方法來(lái)獲取注解值邑退。

    @Pointcut(value = "@annotation(checkAop)")
    private void aAnnotation2(CheckAop checkAop) {
    }

 @After("aAnnotation2(checkAop)")
    public void testaAnnotation2(CheckAop checkAop) {
        Log.e(TAG, "testaAnnotation2---: "+checkAop.value() );
    }
target

用來(lái)匹配的鏈接點(diǎn)所屬目標(biāo)對(duì)象必須是指定類(lèi)型的實(shí)例竹宋。

public interface Human {
    void setGender(int gender);
}

定義接口實(shí)現(xiàn)方法

public class Person implements Human {
    public int gender;

    @Override
    public void setGender(int gender) {
        this.gender = gender;
    }
}

定義調(diào)用代碼

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Person  person = new Person();
        person.setGender(1);
    }
  }

Aspect定義1:target() && call()

    @Before("target(com.zx.aop1.person.Human) && call(* *..setGender(..))")
    public void testThis(JoinPoint joinPoint){
        Log.e(TAG, "testThis ----: "+joinPoint.toString() );
        Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
        Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
    }
target&&call.png

Aspect定義2:target() && execution()

 @Before("target(com.zx.aop1.person.Human) && execution(* *..setGender(..))")
    public void testThis(JoinPoint joinPoint){
        Log.e(TAG, "testThis ----: "+joinPoint.toString() );
        Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
        Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
    }
target && execution.png

結(jié)果分析:當(dāng)使用target()時(shí),不管用 execution()還是call(),Target對(duì)象都是Person,而this對(duì)象是不一樣的地技。

this

用來(lái)匹配鏈接點(diǎn)所屬的對(duì)象引用是某個(gè)特定類(lèi)型的實(shí)例蜈七。

Aspect定義3:target() && call()

 @Before("this(com.zx.aop1.person.Human) && call(* *..setGender(..))")
    public void testThis(JoinPoint joinPoint){
        Log.e(TAG, "testThis ----: "+joinPoint.toString() );
        Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
        Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
    }

結(jié)果:這里是沒(méi)有任何日志輸出的,因?yàn)檫@里是沒(méi)有插入代碼的莫矗。
原因待會(huì)兒再說(shuō)飒硅。

Aspect定義4:this() && execution()

 @Before("this(com.zx.aop1.person.Human) && execution(* *..setGender(..))")
    public void testThis(JoinPoint joinPoint){
        Log.e(TAG, "testThis ----: "+joinPoint.toString() );
        Log.e(TAG, "getTarget() ----: "+joinPoint.getTarget() );
        Log.e(TAG, "getThis() ----: "+joinPoint.getThis() );
    }
this && execution.png

結(jié)果分析:當(dāng)使用this()時(shí)砂缩,如果用 call(),this對(duì)象是空三娩,如果用execution()還是,this對(duì)象都是Person庵芭。原因也很簡(jiǎn)單,本案例的this對(duì)象只能是Human接口的實(shí)現(xiàn)類(lèi)雀监,而當(dāng)你用用 call()時(shí)双吆,插入代碼是在MainActivity中。

targetthis總結(jié):

1会前、target指代的是切點(diǎn)方法的所有者好乐,而this指代的是被織入代碼所屬類(lèi)的實(shí)例對(duì)象。

2瓦宜、如果當(dāng)前要代理的類(lèi)沒(méi)有實(shí)現(xiàn)某個(gè)接口就用 this蔚万;如果實(shí)現(xiàn)了某個(gè)接口,就使用 target临庇。

AspectJ之this和target的區(qū)別(四)

組合切點(diǎn)表達(dá)式
  • &&:要求連接點(diǎn)同時(shí)匹配兩個(gè)切點(diǎn)表達(dá)式

  • ||:要求連接點(diǎn)匹配至少一個(gè)切入點(diǎn)表達(dá)式

  • !:要求連接點(diǎn)不匹配指定的切入點(diǎn)表達(dá)式

匹配所有實(shí)現(xiàn)Human接口的類(lèi)的所有方法且方法的第一個(gè)參數(shù)為int類(lèi)型

@Pointcut("within(com.zx.aop1.person.Human+) && execution(* com.zx.aop1.person...* *(int,..))")

更多切點(diǎn)指示符和切點(diǎn)表達(dá)式的應(yīng)用反璃,請(qǐng)關(guān)注后續(xù)文章!

寫(xiě)文不易苔巨,如果本文對(duì)你有所幫助版扩,請(qǐng)點(diǎn)個(gè)關(guān)注

參考:
AOP 之 AspectJ 全面剖析 in Android
Android AspectJ詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侄泽,一起剝皮案震驚了整個(gè)濱河市礁芦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悼尾,老刑警劉巖柿扣,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異闺魏,居然都是意外死亡未状,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)析桥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)司草,“玉大人,你說(shuō)我怎么就攤上這事泡仗÷窈纾” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵娩怎,是天一觀(guān)的道長(zhǎng)搔课。 經(jīng)常有香客問(wèn)我,道長(zhǎng)截亦,這世上最難降的妖魔是什么爬泥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任柬讨,我火速辦了婚禮,結(jié)果婚禮上袍啡,老公的妹妹穿的比我還像新娘踩官。我一直安慰自己,他們只是感情好葬馋,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布卖鲤。 她就那樣靜靜地躺著,像睡著了一般畴嘶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上集晚,一...
    開(kāi)封第一講書(shū)人閱讀 52,807評(píng)論 1 314
  • 那天窗悯,我揣著相機(jī)與錄音,去河邊找鬼偷拔。 笑死蒋院,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莲绰。 我是一名探鬼主播欺旧,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蛤签!你這毒婦竟也來(lái)了辞友?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤震肮,失蹤者是張志新(化名)和其女友劉穎称龙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體戳晌,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲫尊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沦偎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疫向。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖豪嚎,靈堂內(nèi)的尸體忽然破棺而出搔驼,到底是詐尸還是另有隱情,我是刑警寧澤疙渣,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布匙奴,位于F島的核電站,受9級(jí)特大地震影響妄荔,放射性物質(zhì)發(fā)生泄漏泼菌。R本人自食惡果不足惜谍肤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哗伯。 院中可真熱鬧荒揣,春花似錦、人聲如沸焊刹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)虐块。三九已至俩滥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贺奠,已是汗流浹背霜旧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留儡率,地道東北人挂据。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像儿普,于是被迫代替她去往敵國(guó)和親崎逃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361

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