Spring aop<4>

AOP(Aspect Oriented Programing)
面向切面編程采用橫向抽取機(jī)制,以取代傳統(tǒng)的縱向繼承體系的重復(fù)性代碼(如性能監(jiān)控/事務(wù)管理/安全檢查/緩存實(shí)現(xiàn)等)

Spring 實(shí)現(xiàn)AOP方式有cglib和Java動(dòng)態(tài)代理栅贴,默認(rèn)實(shí)現(xiàn)方式為Java動(dòng)態(tài)代理。

AOP相關(guān)術(shù)語

  • Joinpoint:連接點(diǎn) 指那些被攔截到的點(diǎn).在Spring中,這些點(diǎn)指方法(因?yàn)镾pring只支持方法類型的連接點(diǎn)).
  • Pointcut:切入點(diǎn) 指需要(配置)被增強(qiáng)的Joinpoint
  • Advice:通知/增強(qiáng) 指攔截到Joinpoint后要做的操作.通知分為前置通知/后置通知/異常通知/最終通知/環(huán)繞通知等.
  • Aspect:切面 切入點(diǎn)和通知的結(jié)合.
  • Target:目標(biāo)對象 需要被代理(增強(qiáng))的對象.
  • Proxy:代理對象 目標(biāo)對象被AOP 織入 增強(qiáng)/通知后,產(chǎn)生的對象.
  • Weaving:織入 指把增強(qiáng)/通知應(yīng)用到目標(biāo)對象來創(chuàng)建代理對象的過程(Spring采用動(dòng)態(tài)代理織入,AspectJ采用編譯期織入和類裝載期織入).
  • Introduction: 引介 一種特殊通知,在不修改類代碼的前提下,可以在運(yùn)行期為類動(dòng)態(tài)地添加一些Method/Field(不常用).
  1. 引入圖中的包


    image.png
  2. 在spring配置文件中配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"    
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd>
 <!--和schema一起啟動(dòng)@Aspectj支持-->
    <aop:aspectj-autoproxy/>

或者

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"    
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd>
 <!--第二種方式啟動(dòng)@Aspectj支持-->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
</beans>
  1. 使用@Aspect定義一個(gè)切面類
@Aspect
public class TestAspectModel {
...
}
  1. 定義一個(gè)需要增強(qiáng)的類
@Component(value = "firstaspect")
public class Firstaspect {
    public void noReturn() {
        System.out.println("執(zhí)行方法noReturn()");
    }
    public String hasReturn(String argument) {
        System.out.println("執(zhí)行方法hasReturn()"+argument);
        return " (hasReturn方法執(zhí)行返回值) "+argument;
    }

    public void throwException() {
        System.out.println("執(zhí)行方法throwException()");
        throw new IllegalArgumentException("i am a runtime exception");
    }
}
  1. 定義增強(qiáng)方法
    增強(qiáng)方式有:

@Before
@AfterReturning
@AfterThrowing
@After
@Around

5.1 @Before

@Aspect
public class TestAspectModel {

    @Before("execution(* com.lq.play.model.*.*(..))")
    public void before() {
        System.out.println("Before!");
    }
}

測試用例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {

    @Autowired
    private Firstaspect firstaspect;

    @Test
    public void test() throws Exception {
        firstaspect.hasReturn("");
    }
}

執(zhí)行結(jié)果:

Before!
執(zhí)行方法hasReturn()

@Before 增強(qiáng)只能在目標(biāo)方法執(zhí)行之前織入增強(qiáng)获高,如果需要阻止目標(biāo)方法執(zhí)行船响,可通過拋出一個(gè)異常來實(shí)現(xiàn)躬拢。
5.2 @AfterReturning
@AfterReturning增強(qiáng)方法將在目標(biāo)方法正常完成后被織入躲履。
@AfterReturning注解有兩個(gè)屬性:

  • pointcut/value:兩個(gè)屬性作用相同,既可以是一個(gè)已有的切入點(diǎn)聊闯,也可以是一個(gè)切入點(diǎn)表達(dá)式工猜,當(dāng)指定了pointcut屬性之后,value值將被覆蓋菱蔬。
  • returning:指定一個(gè)形參名篷帅,該形參可用于訪問方法返回值。在Advice方法中可以指定形參的類型拴泌,會(huì)限制目標(biāo)方法必須返回指定類型的值或者沒有返回值魏身。
    在TestAspectModel 中增加一個(gè)織入方法:
@AfterReturning(returning = "returnValue",pointcut = "execution(* com.lq.play.model.*.*(..))")
//@AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
public void afterReturning(JoinPoint joinPoint,Object returnValue) {
    System.out.println("afterReturning!");
    System.out.println("afterReturning!: 目標(biāo)方法返回值:"+returnValue);
    System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法為:"+joinPoint.getSignature().getName());
    System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:"+joinPoint.getArgs());
    System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)對象為:" +joinPoint.getTarget());
}

執(zhí)行結(jié)果:

執(zhí)行方法hasReturn()
afterReturning!
afterReturning!: 目標(biāo)方法返回值: (hasReturn方法執(zhí)行返回值)
afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法為:hasReturn
afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:[Ljava.lang.Object;@2a8448fa
afterReturning! 被織入增強(qiáng)處理的目標(biāo)對象為:com.lq.play.model.Firstaspect@6f204a1a

5.3 @AfterThrowing
@AfterThrowing增強(qiáng)方法將在目標(biāo)方法正常完成后被織入。
@AfterThrowing注解有兩個(gè)屬性:

  • pointcut/value:兩個(gè)屬性作用相同弛针,既可以是一個(gè)已有的切入點(diǎn)叠骑,也可以是一個(gè)切入點(diǎn)表達(dá)式,當(dāng)指定了pointcut屬性之后削茁,value值將被覆蓋宙枷。
  • throwing:指定一個(gè)形參名,該形參可用于訪問目標(biāo)方法拋出的異常茧跋。在Advice方法中可以指定形參的類型慰丛,會(huì)限制目標(biāo)方法必須拋出指定類型的異常。
    向TestAspectModel增加織入方法:
 @AfterThrowing(throwing = "ex",pointcut = "execution(* com.lq.play.model.*.*(..))")
    public void afterThrowing(IllegalArgumentException ex) {
        System.out.println("afterThrowing:"+ex);
    }

向Firstaspect添加拋異常方法

 public void throwException() {
        System.out.println("執(zhí)行方法throwException()");
        throw new IllegalArgumentException("i am a runtime exception");
    }

測試用例

@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {

    @Autowired
    private Firstaspect firstaspect;

    @Test
    public void test() throws Exception {
//        firstaspect.hasReturn("");
        firstaspect.throwException();
    }
}

返回結(jié)果:

執(zhí)行方法throwException()
afterThrowing:java.lang.IllegalArgumentException: i am a runtime exception

java.lang.IllegalArgumentException: i am a runtime exceptionat com.lq.play.model.Firstaspect.throwException(Firstaspect.java:22)at com.lq.play.model.Firstaspect$$FastClassBySpringCGLIB$$89d307c5.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)...

5.4 @After
@AfterReturning增強(qiáng)處理在目標(biāo)方法成功執(zhí)行后才會(huì)被織入瘾杭。
@After增強(qiáng)處理不管目標(biāo)方法成功與否诅病,它都會(huì)被織入,和異常捕捉的finally關(guān)鍵字類似粥烁。
增強(qiáng)處理類:
```java
@Aspect
public class TestAspectModel {

    @After("execution(* com.lq.play.model.*.*(..))")
    public void after() {
        System.out.println("After!");
    }

    @AfterReturning(returning = "returnValue", pointcut = "execution(* com.lq.play.model.*.*(..))")
    public void afterReturning(JoinPoint joinPoint, Object returnValue) {
        System.out.println("afterReturning!");
        System.out.println("afterReturning!: 目標(biāo)方法返回值:" + returnValue);
        System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法為:" + joinPoint.getSignature().getName());
        System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:" + joinPoint.getArgs());
        System.out.println("afterReturning! 被織入增強(qiáng)處理的目標(biāo)對象為:" + joinPoint.getTarget());
    }
}

測試用例

@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {

    @Autowired
    private Firstaspect firstaspect;

    @Test
    public void test() throws Exception {
//        firstaspect.hasReturn("");
        firstaspect.throwException();
    }
}

返回結(jié)果

執(zhí)行方法throwException()
After!
java.lang.IllegalArgumentException: i am a runtime exception
at com.lq.play.model.Firstaspect.throwException(Firstaspect.java:22)at com.lq.play.model.Firstaspect$$FastClassBySpringCGLIB$$89d307c5.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)

執(zhí)行了@After增強(qiáng)贤笆,沒有執(zhí)行@AfterReturning

5.6 @Around
這個(gè)是功能最強(qiáng)大的增強(qiáng)處理,既可以在執(zhí)行方法前織入增強(qiáng)動(dòng)作讨阻,也可以在執(zhí)行目標(biāo)方法之后織入增強(qiáng)動(dòng)作芥永。

  • @Around可以決定目標(biāo)方法在什么時(shí)候執(zhí)行,如何執(zhí)行钝吮,甚至可以組織目標(biāo)方法的執(zhí)行埋涧。
  • @Around可以改變執(zhí)行目標(biāo)方法的參數(shù)值,也可以改標(biāo)執(zhí)行目標(biāo)方法之后的返回值奇瘦。
  • @Around增強(qiáng)方法的第一個(gè)參數(shù)必須是ProceedingJoinPoint類型(至少包含一個(gè)形參)棘催,如果程序沒有調(diào)用ProceedingJoinPoint 參數(shù)的proceed方法,則目標(biāo)方法不會(huì)被執(zhí)行耳标。
  • 調(diào)用ProceedingJoinPoint 參數(shù)的proceed方法時(shí)醇坝,可以傳入一個(gè)Object[]對象作為參數(shù),該數(shù)組中的值將被傳入目標(biāo)方法為執(zhí)行方法的實(shí)參次坡。
    織入方法:
@Aspect
public class TestAspectModel {
    @Around("execution(* com.lq.play.model.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{

        System.out.println("around!");
        System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)方法為:"+pjp.getSignature().getName());
        System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:"+Arrays.toString(pjp.getArgs()));
        System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)對象為:" +pjp.getTarget());
        System.out.println("around!");
        Object[] args = pjp.getArgs();
        if (args != null && args.length > 0) {
            args[0]="around 增加的參數(shù)前綴"+args[0];
        }

        Object returnValue =  pjp.proceed(args);

        return "around 增加的執(zhí)行后前綴"+(String)returnValue;
    }
}

目標(biāo)方法

   public String hasReturn(String argument) {
        System.out.println("執(zhí)行方法hasReturn()"+"傳入的參數(shù)值:"+argument);
        return "(hasReturn方法執(zhí)行返回值)"+argument;
    }

測試用例:

   System.out.println(firstaspect.hasReturn(""));

執(zhí)行結(jié)果:

around!
around! 被織入增強(qiáng)處理的目標(biāo)方法為:hasReturn
around! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:[Ljava.lang.Object;@533bda92
around! 被織入增強(qiáng)處理的目標(biāo)對象為:com.lq.play.model.Firstaspect@304bb45b
around!
執(zhí)行方法hasReturn()傳入的參數(shù)值:around 增加的參數(shù)前綴
around 增加的執(zhí)行后前綴(hasReturn方法執(zhí)行返回值)around 增加的參數(shù)前綴

五個(gè)增強(qiáng)都加入執(zhí)行結(jié)果

around!
around! 被織入增強(qiáng)處理的目標(biāo)方法為:hasReturn
around! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:[Ljava.lang.Object;@7bd7d6d6
around! 被織入增強(qiáng)處理的目標(biāo)對象為:com.lq.play.model.Firstaspect@43f02ef2
around!
Before!
執(zhí)行方法hasReturn()傳入的參數(shù)值:around 增加的參數(shù)前綴
After!
afterReturning!
afterReturning!: 目標(biāo)方法返回值:around 增加的執(zhí)行后前綴(hasReturn方法執(zhí)行返回值)around 增加的參數(shù)前綴
afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法為:hasReturn
afterReturning! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:[Ljava.lang.Object;@5b7a7f33
afterReturning! 被織入增強(qiáng)處理的目標(biāo)對象為:com.lq.play.model.Firstaspect@43f02ef2

執(zhí)行了

  • @Around
  • @Before
  • 目標(biāo)方法
  • @After
  • @AfterReturning
  1. 訪問目標(biāo)方法的參數(shù)
    在增強(qiáng)方法里第一個(gè)參數(shù)定義為JoinPoint類型(ProceedingJoinPoint 即為JoinPoint子類)呼猪,JoinPoint代表了織入增強(qiáng)處理的連接點(diǎn)呀袱。JoinPoint包含如下幾個(gè)方法。
  • Object[] getArgs():返回目標(biāo)方法執(zhí)行時(shí)的參數(shù)郑叠。
  • Signature getSignature():返回被增強(qiáng)的方法的相關(guān)信息。
  • Object getTarget():返回被織入增強(qiáng)處理的目標(biāo)對象明棍。
  • Object getThis(): 返回AOP框架為目標(biāo)對象生成的代理對象乡革。

示例如@Around的增強(qiáng)方法:

 @Around("execution(* com.lq.play.model.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println("around!");
        System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)方法為:" + pjp.getSignature().getName());
        System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)方法的參數(shù)為:" + Arrays.toString(pjp.getArgs()));
        System.out.println("around! 被織入增強(qiáng)處理的目標(biāo)對象為:" + pjp.getTarget());
        System.out.println("around!");
        Object[] args = pjp.getArgs();
        if (args != null && args.length > 0) {
            args[0] = "around 增加的參數(shù)前綴" + args[0];
        }

        Object returnValue = pjp.proceed(args);

        return "around 增加的執(zhí)行后前綴" + (String) returnValue;
    }

可以指定織入優(yōu)先級

  • 讓切面類實(shí)現(xiàn)org.springframework.core.Ordered接口,實(shí)現(xiàn)該接口值需要實(shí)現(xiàn)一個(gè)int getOrder()方法摊腋,該方法返回值越小沸版,則優(yōu)先級越高。
  • 直接使用@Order注解來修飾一個(gè)切面類兴蒸,指定一個(gè)int類型的value屬性视粮,該方法返回值越小,則優(yōu)先級越高橙凳。

可以為表達(dá)式指定參數(shù),如下:

 @AfterReturning(returning = "returnValue", pointcut = "execution(* com.lq.play.model.*.*(..))&&args(arg1)")
//@AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
    public void afterReturning(JoinPoint joinPoint, Object returnValue,String arg1) {}
  1. 定義切入點(diǎn)
    當(dāng)多個(gè)表達(dá)式相同時(shí)蕾殴,可以定義一個(gè)
 @Pointcut("execution(* com.lq.play.model.*.*(..))")
 public void myPointCut() {
 }

然后其它引入即可

 @AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
  1. 也可以使用xml配置
  <bean name="testAspectModel" class="com.lq.play.aspect.TestAspectModel" />
    <!-- 進(jìn)行 aop 的配置 -->
    <aop:config><!-- 配置切入點(diǎn)表達(dá)式 :哪些類的方法需要進(jìn)行增強(qiáng) 哪些類的方法需要進(jìn)行增強(qiáng) -->
        <aop:pointcut expression="execution(* com.lq.play.model.*.*(..))" id="pointcut1"/>
        <aop:pointcut expression="execution(* com.lq.play.model.*.*(..))" id="pointcut2"/>
        <!-- 配置切面 -->
        <aop:aspect ref="testAspectModel">
            <aop:before method="before" pointcut-ref ="pointcut1" />
            <aop:after-returning method ="afterReturning" pointcut-ref ="pointcut1" returning="returnValue" />
            <aop:around method="around" pointcut-ref ="pointcut1" />
            <aop:after-throwing method ="afterThrowing" pointcut-ref ="pointcut1" throwing="ex"/>
            <aop:after method="after" pointcut-ref ="pointcut1" />
        </aop:aspect>
    </aop:config>

<aop:aspect .../>有如下三個(gè)屬性:

  • id定義改切面的標(biāo)識名

  • ref用于將ref屬性所引用的普通Bean轉(zhuǎn)換為切面Bean.

  • order:指定該切面Bean的優(yōu)先級,值越小岛啸,優(yōu)先級越高钓觉。

  • <aop:before.../>

  • <aop:after-returning.../>

  • <aop:before.../>

  • <aop:after-throwing .../>

  • <aop:after.../>

這幾個(gè)元素都不支持子元素,但有如下屬性:

  • pointcut 織入點(diǎn)表達(dá)式
  • pointcut-ref 和pointcut只能有一個(gè)坚踩,指定一個(gè)pointcut
  • method 指定將該方法轉(zhuǎn)為增強(qiáng)處理方法
  • throwing 指定拋出的異常參數(shù)
  • returning 目標(biāo)方法返回值
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荡灾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瞬铸,更是在濱河造成了極大的恐慌批幌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗓节,死亡現(xiàn)場離奇詭異荧缘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赦政,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門胜宇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恢着,你說我怎么就攤上這事桐愉。” “怎么了掰派?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵从诲,是天一觀的道長。 經(jīng)常有香客問我靡羡,道長系洛,這世上最難降的妖魔是什么俊性? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮描扯,結(jié)果婚禮上定页,老公的妹妹穿的比我還像新娘。我一直安慰自己绽诚,他們只是感情好典徊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恩够,像睡著了一般卒落。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜂桶,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天儡毕,我揣著相機(jī)與錄音,去河邊找鬼扑媚。 笑死腰湾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的钦购。 我是一名探鬼主播檐盟,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼押桃!你這毒婦竟也來了葵萎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤唱凯,失蹤者是張志新(化名)和其女友劉穎羡忘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磕昼,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卷雕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了票从。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漫雕。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖峰鄙,靈堂內(nèi)的尸體忽然破棺而出浸间,到底是詐尸還是另有隱情,我是刑警寧澤吟榴,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布魁蒜,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏兜看。R本人自食惡果不足惜锥咸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望细移。 院中可真熱鬧搏予,春花似錦、人聲如沸弧轧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劣针。三九已至,卻和暖如春亿扁,著一層夾襖步出監(jiān)牢的瞬間捺典,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工从祝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留襟己,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓牍陌,卻偏偏與公主長得像擎浴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子毒涧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354

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