37--SpringAop代理調(diào)用過(guò)程(一)

經(jīng)過(guò)前兩篇的分析,已經(jīng)成功創(chuàng)建了目標(biāo)類(lèi)的代理突琳,接著分析代理的調(diào)用過(guò)程。在前面的章節(jié)已經(jīng)介紹過(guò)SpringAOP中的增強(qiáng)類(lèi)型分別有前置增強(qiáng)、后置異常增強(qiáng)、后置返回增強(qiáng)菇绵、后置最終增強(qiáng)欠动、環(huán)繞增強(qiáng)五種類(lèi)型人芽,從名稱(chēng)上我們也可以大致看出來(lái)前置增強(qiáng)一定是先于后置增強(qiáng)被執(zhí)行的,那么SpringAOP是如何保證這幾種增強(qiáng)的執(zhí)行順序呢矢劲?它們的執(zhí)行順序應(yīng)該什么樣呢?

35--SpringAop創(chuàng)建代理(一) 中已經(jīng)介紹過(guò)Spring在獲取到所有增強(qiáng)之后,還要篩選出來(lái)適合當(dāng)前bean的增強(qiáng)捎谨。

再篩選完之后,其實(shí)還有兩個(gè)很重要的步驟,我們沒(méi)有分析臂寝,那就是在eligibleAdvisors集合首位加入ExposeInvocationInterceptor增強(qiáng)(方法攔截)以及對(duì)增強(qiáng)進(jìn)行排序

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 1、查找所有候選增強(qiáng)
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 2、從所有增強(qiáng)集合中查找適合當(dāng)前bean的增強(qiáng)
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    // 3怜珍、在eligibleAdvisors集合首位加入ExposeInvocationInterceptor增強(qiáng)
    // ExposeInvocationInterceptor的作用是可以將當(dāng)前的MethodInvocation暴露為一個(gè)thread-local對(duì)象,該攔截器很少使用
    // 使用場(chǎng)景:一個(gè)切點(diǎn)(例如AspectJ表達(dá)式切點(diǎn))需要知道它的全部調(diào)用上線文環(huán)境
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        // 4.對(duì)增強(qiáng)進(jìn)行排序
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

關(guān)于ExposeInvocationInterceptor無(wú)需過(guò)多了解柔袁,注釋里已經(jīng)有所介紹插掂,重點(diǎn)看對(duì)增強(qiáng)的排序原則。

protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
    // 1.創(chuàng)建PartiallyComparableAdvisorHolder集合并將所有的增強(qiáng)加入到該集合中
    List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors = new ArrayList<>(advisors.size());
    for (Advisor element : advisors) {
        partiallyComparableAdvisors.add(new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));
    }
    // 2.執(zhí)行排序并返回結(jié)果
    List<PartiallyComparableAdvisorHolder> sorted = PartialOrder.sort(partiallyComparableAdvisors);
    if (sorted != null) {
        List<Advisor> result = new ArrayList<>(advisors.size());
        for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
            result.add(pcAdvisor.getAdvisor());
        }
        return result;
    }
    else {
        return super.sortAdvisors(advisors);
    }
}

具體的排序方法不做過(guò)深入的分析了构回,但是其排序的原則和增強(qiáng)的執(zhí)行順序還是要簡(jiǎn)單介紹一下:

  1. 如果在一個(gè)切面中沒(méi)有相同類(lèi)型的增強(qiáng)(例如:一個(gè)切面類(lèi)里不同時(shí)有兩個(gè)前置增強(qiáng))患民,且目標(biāo)方法里沒(méi)有異常拋出仅孩,那么其執(zhí)行順序是:環(huán)繞增強(qiáng) --> 前置增強(qiáng) --> 目標(biāo)類(lèi)方法 --> 前置增強(qiáng) --> 后置最終增強(qiáng) --> 后置返回增強(qiáng)
  2. 如果在一個(gè)切面中沒(méi)有相同類(lèi)型的增強(qiáng)(例如:一個(gè)切面類(lèi)里不同時(shí)有兩個(gè)前置增強(qiáng))赦肃,但目標(biāo)方法里存在異常船侧,那么其執(zhí)行順序是 :環(huán)繞增強(qiáng) --> 前置增強(qiáng) --> 目標(biāo)類(lèi)方法(直至調(diào)用到發(fā)生異常的代碼)--> 后置最終增強(qiáng)--> 后置異常增強(qiáng)
  3. 如果兩條相同增強(qiáng)類(lèi)型增強(qiáng)來(lái)自同一切面厅各,它們將具有相同的順序队塘。然后根據(jù)以下規(guī)則進(jìn)一步排序锯梁。如果是后置增強(qiáng)蕊肥,那么最后聲明的增強(qiáng)將獲得最高的優(yōu)先級(jí)耘成,對(duì)于其他類(lèi)型的增強(qiáng),首先聲明的增強(qiáng)將獲得最高的優(yōu)先級(jí)

其執(zhí)行順序已經(jīng)了解了瘪菌,下面通過(guò)兩個(gè)實(shí)例才驗(yàn)證一下(另外在前面的文章介紹稍微有些缺陷撒会,這里也修正一下)。

2. 增強(qiáng)調(diào)用順序?qū)嵗?/h5>
  • 目標(biāo)類(lèi)
package com.lyc.cn.v2.day07;

public interface Animal {
    void sayHello();

    void sayException();
}

package com.lyc.cn.v2.day07;

public class Dog implements Animal {

    @Override
    public void sayHello() {
        System.out.println("--被增強(qiáng)的方法");

    }

    @Override
    public void sayException() {
        // 手動(dòng)拋出異常
        System.out.println("--被增強(qiáng)的方法,準(zhǔn)備拋出異常");
        throw new IllegalArgumentException();
    }
}
  • 切面類(lèi)
package com.lyc.cn.v2.day07;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.junit.experimental.theories.Theory;

/**
 * 切面類(lèi)
 * @author: LiYanChao
 * @create: 2018-10-31 15:46
 */
@Aspect
//@Aspect("perthis(this(com.lyc.cn.v2.day07.Dog))")
//@Aspect("pertarget(this(com.lyc.cn.v2.day07.Dog))")
public class DogAspect {

    /**
     * 例如:execution (* com.sample.service.impl..*.*(..)
     * 1师妙、execution(): 表達(dá)式主體茧彤。
     * 2、第一個(gè)*號(hào):表示返回類(lèi)型疆栏,*號(hào)表示所有的類(lèi)型曾掂。
     * 3、包名:表示需要攔截的包名壁顶,后面的兩個(gè)點(diǎn)表示當(dāng)前包和當(dāng)前包的所有子包珠洗,
     * 即com.sample.service.impl包、子孫包下所有類(lèi)的方法若专。
     * 4许蓖、第二個(gè)*號(hào):表示類(lèi)名,*號(hào)表示所有的類(lèi)调衰。
     * 5膊爪、*(..):最后這個(gè)星號(hào)表示方法名,*號(hào)表示所有的方法嚎莉,后面括弧里面表示方法的參數(shù)米酬,兩個(gè)點(diǎn)表示任何參數(shù)。
     **/
    @Pointcut("execution(* com.lyc.cn.v2.day07.*.*(..))")
    public void test() {

    }

    @Before("test()")
    public void beforeTest() {
        System.out.println("==前置增強(qiáng)");
    }

    @After("test()")
    public void afterTest() {
        System.out.println("==后置最終增強(qiáng)");
    }

    @AfterThrowing(value = "test()", throwing = "th")
    public void afterThrowingTest(JoinPoint jp, Throwable th) {
        System.out.println("==后置異常增強(qiáng),連接點(diǎn)信息:" + jp.getSignature());
        System.out.println("==后置異常增強(qiáng),異常信息:" + th);
    }

    @AfterReturning("test()")
    public void afterReturningTest() {
        System.out.println("==后置返回增強(qiáng)");
    }
   
    // 如果在環(huán)繞增強(qiáng)里手動(dòng)處理了異常的話,那么后置異常增強(qiáng)是無(wú)法被調(diào)用到的
    @Around("test()")
    public Object aroundTest(ProceedingJoinPoint p) throws Throwable {
        System.out.println("==環(huán)繞增強(qiáng)開(kāi)始");
        Object o = p.proceed();
        System.out.println("==環(huán)繞增強(qiáng)結(jié)束");
        return o;
    }

    //  @DeclareParents(value = "com.lyc.cn.v2.day07.Dog", defaultImpl = IntroduceImpl.class)
    //  private IIntroduce iIntroduce;


}
  • 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--
        1趋箩、proxy-target-class
            如果被代理的目標(biāo)對(duì)象至少實(shí)現(xiàn)了一個(gè)接口赃额,則會(huì)使用JDK動(dòng)態(tài)代理加派,所有實(shí)現(xiàn)該目標(biāo)類(lèi)實(shí)現(xiàn)的接口都將被代理
            如果該目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)任何接口,則創(chuàng)建CGLIB動(dòng)態(tài)代理跳芳。
            但是可以通過(guò)proxy-target-class屬性強(qiáng)制指定使用CGLIB代理芍锦,
        2、expose-proxy
            解決目標(biāo)對(duì)象內(nèi)部的自我調(diào)用無(wú)法實(shí)施切面增強(qiáng)的問(wèn)題
    -->
    <aop:aspectj-autoproxy>
        <!-- 指定@Aspect類(lèi)飞盆,支持正則表達(dá)式娄琉,符合該表達(dá)式的切面類(lèi)才會(huì)被應(yīng)用-->
        <!--<aop:include name="dogAspect"></aop:include>-->
    </aop:aspectj-autoproxy>

    <!--AspectJ-->
    <bean name="dogAspect" class="com.lyc.cn.v2.day07.DogAspect"/>
    <!--<bean name="catAspect" class="com.lyc.cn.v2.day07.CatAspect"/>-->

    <!--bean-->
    <bean id="dog" class="com.lyc.cn.v2.day07.Dog"/>

</beans>
  • 測(cè)試方法
@Test
public void test1() {
    // 基于@AspectJ注解方式
    ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day07.xml");
    Animal dog = ctx.getBean("dog", Animal.class);
    dog.sayHello();
}

@Test
public void test5() {
    // 基于@AspectJ注解方式
    ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day07.xml");
    Animal dog = ctx.getBean("dog", Animal.class);
    dog.sayException();
}

這樣準(zhǔn)備工作做完了,來(lái)看具體的測(cè)試結(jié)果:

  • 沒(méi)有異常拋出
==環(huán)繞增強(qiáng)開(kāi)始
==前置增強(qiáng)
--被增強(qiáng)的方法
==環(huán)繞增強(qiáng)結(jié)束
==后置最終增強(qiáng)
==后置返回增強(qiáng)
  • 有異常拋出
==環(huán)繞增強(qiáng)開(kāi)始
==前置增強(qiáng)
--被增強(qiáng)的方法,準(zhǔn)備拋出異常
==后置最終增強(qiáng)
==后置異常增強(qiáng),連接點(diǎn)信息:void com.lyc.cn.v2.day07.Animal.sayException()
==后置異常增強(qiáng),異常信息:java.lang.IllegalArgumentException

對(duì)于增強(qiáng)的執(zhí)行順序我們已經(jīng)有所了解吓歇,下面通過(guò)分析源碼车胡,來(lái)看一下Spring是如何執(zhí)行代理方法調(diào)用的。

3.JDK動(dòng)態(tài)代理調(diào)用過(guò)程(假設(shè)無(wú)異常拋出情況下)

這里照瘾,如何進(jìn)入到其調(diào)用源碼里呢匈棘,我們可以在前面的測(cè)試類(lèi)dog.sayHello();這句話前面打斷點(diǎn),然后步入斷點(diǎn)就OK了析命。

/**
 * 調(diào)用JDK動(dòng)態(tài)代理invoke方法
 * Implementation of {@code InvocationHandler.invoke}.
 * <p>Callers will see exactly the exception thrown by the target,
 * unless a hook method throws an exception.
 */
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
        // 1主卫、處理equals方法,如果接口中沒(méi)有定義equals而在實(shí)現(xiàn)類(lèi)中覆蓋了equals方法鹃愤,那么該equals方法不會(huì)被增強(qiáng)
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        // 2簇搅、處理hashCode方法,如果接口中沒(méi)有定義hashCode而在實(shí)現(xiàn)類(lèi)中覆蓋了hashCode方法软吐,那么該hashCode方法不會(huì)被增強(qiáng)
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        // 3瘩将、如果目標(biāo)對(duì)象是DecoratingProxy類(lèi)型赠群,則返回目標(biāo)對(duì)象的最終對(duì)象類(lèi)型
        // DecoratingProxy接口只有一個(gè)getDecoratedClass方法蚌父,用于返回目標(biāo)對(duì)象的最終對(duì)象類(lèi)型
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            // There is only getDecoratedClass() declared -> dispatch to proxy config.
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        // 4、如果目標(biāo)對(duì)象是Advice類(lèi)型岸军,則直接使用反射進(jìn)行調(diào)用
        // opaque-->標(biāo)記是否需要阻止通過(guò)該配置創(chuàng)建的代理對(duì)象轉(zhuǎn)換為Advised類(lèi)型肖抱,默認(rèn)值為false备典,表示代理對(duì)象可以被轉(zhuǎn)換為Advised類(lèi)型
        // method.getDeclaringClass().isInterface()-->目標(biāo)對(duì)象是接口
        // method.getDeclaringClass().isAssignableFrom(Advised.class)-->
        // 是用來(lái)判斷一個(gè)類(lèi)Class1和另一個(gè)類(lèi)Class2是否相同或者Class1類(lèi)是不是Class2的父類(lèi)。例如:Class1.isAssignableFrom(Class2)
        else if (!this.advised.opaque
                && method.getDeclaringClass().isInterface()
                && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;

        // 5意述、解決目標(biāo)對(duì)象內(nèi)部自我調(diào)用無(wú)法實(shí)施切面增強(qiáng)提佣,在這里暴露代理
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);

        // Get the interception chain for this method.
        // 6、獲取當(dāng)前方法的攔截器鏈荤崇,并執(zhí)行調(diào)用
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // 檢測(cè)是否攔截器鏈?zhǔn)欠駷榭瞻杵粒绻麛r截器鏈為空,那么直接通過(guò)反射調(diào)用目標(biāo)對(duì)象的方法术荤,避免創(chuàng)建MethodInvocation
        // Check whether we have any advice. If we don't, we can fallback on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            // 通過(guò)反射直接調(diào)用目標(biāo)對(duì)象的方法
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // 創(chuàng)建MethodInvocation對(duì)象并調(diào)用proceed方法倚喂,攔截器鏈被封裝到了invocation中
            // We need to create a method invocation...
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            // 調(diào)用攔截器鏈
            retVal = invocation.proceed();
        }

        // 7、返回結(jié)果
        // Massage return value if necessary.
        Class<?> returnType = method.getReturnType();
        if (retVal != null
                && retVal == target
                && returnType != Object.class
                && returnType.isInstance(proxy)
                && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

具體的執(zhí)行過(guò)程喜每,注釋里已經(jīng)分析的很清楚了务唐,最核心的就是獲取攔截器鏈和執(zhí)行攔截器鏈調(diào)用雳攘。留在下一章分析带兜。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末枫笛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子刚照,更是在濱河造成了極大的恐慌刑巧,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件无畔,死亡現(xiàn)場(chǎng)離奇詭異啊楚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)浑彰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)恭理,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人郭变,你說(shuō)我怎么就攤上這事颜价。” “怎么了诉濒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵周伦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我未荒,道長(zhǎng)专挪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任片排,我火速辦了婚禮寨腔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘率寡。我一直安慰自己脆侮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布勇劣。 她就那樣靜靜地躺著靖避,像睡著了一般。 火紅的嫁衣襯著肌膚如雪比默。 梳的紋絲不亂的頭發(fā)上幻捏,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音命咐,去河邊找鬼篡九。 笑死,一個(gè)胖子當(dāng)著我的面吹牛醋奠,可吹牛的內(nèi)容都是我干的榛臼。 我是一名探鬼主播伊佃,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沛善!你這毒婦竟也來(lái)了航揉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤金刁,失蹤者是張志新(化名)和其女友劉穎帅涂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體尤蛮,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡媳友,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了产捞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片醇锚。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖坯临,靈堂內(nèi)的尸體忽然破棺而出焊唬,到底是詐尸還是另有隱情,我是刑警寧澤尿扯,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布求晶,位于F島的核電站,受9級(jí)特大地震影響衷笋,放射性物質(zhì)發(fā)生泄漏芳杏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一辟宗、第九天 我趴在偏房一處隱蔽的房頂上張望爵赵。 院中可真熱鬧,春花似錦泊脐、人聲如沸空幻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秕铛。三九已至,卻和暖如春缩挑,著一層夾襖步出監(jiān)牢的瞬間但两,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工供置, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谨湘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像紧阔,于是被迫代替她去往敵國(guó)和親坊罢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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