Spring AOP - 注解方式使用介紹(長文詳解)

前言

之前的源碼解析章節(jié)揣炕,本人講解了Spring IOC 的核心部分的源碼稠腊。如果你熟悉Spring AOP的使用的話,在了解Spring IOC的核心源碼之后架忌,學(xué)習(xí)Spring AOP 的源碼,應(yīng)該可以說是水到渠成叹放,不會(huì)有什么困難挠羔。

但是直接開始講Spring AOP的源碼,本人又覺得有點(diǎn)突兀褥赊,所以便有了這一章。Spring AOP 的入門使用介紹:包括Spring AOP的一些概念性介紹和配置使用方法拌喉。

這里先貼一下思維導(dǎo)圖俐银。

Spring AOP.png

AOP 是什么

AOP : 面向切面編程(Aspect Oriented Programming)

Aspect是一種新的模塊化機(jī)制,用來描述分散在對象田藐、類或函數(shù)中的橫切關(guān)注點(diǎn)(crosscutting concern)。從關(guān)注點(diǎn)中分離出橫切關(guān)注點(diǎn)是面向切面的程序設(shè)計(jì)的核心概念汽久。分離關(guān)注點(diǎn)使解決特定領(lǐng)域問題的代碼從業(yè)務(wù)邏輯中獨(dú)立出來,業(yè)務(wù)邏輯的代碼中不再含有針對特定領(lǐng)域問題代碼的調(diào)用景醇,業(yè)務(wù)邏輯同特定領(lǐng)域問題的關(guān)系通過切面來封裝、維護(hù)吧寺,這樣原本分散在整個(gè)應(yīng)用程序中的變動(dòng)就可以很好地管理起來。

最近在看李智慧的《大型網(wǎng)站技術(shù)架構(gòu)》一書中散劫,作者提到,開發(fā)低耦合系統(tǒng)是軟件設(shè)計(jì)的終極目標(biāo)之一赖条。AOP這種面向切面編程的的方式就體現(xiàn)了這樣的理念。將一些重復(fù)的纬乍、和業(yè)務(wù)主邏輯不相關(guān)的功能性代碼(日志記錄、安全管理等)通過切面模塊化地抽離出來進(jìn)行封裝蕾额,實(shí)現(xiàn)關(guān)注點(diǎn)分離彼城、模塊解耦,使得整個(gè)系統(tǒng)更易于維護(hù)管理募壕。

這樣分而治之的設(shè)計(jì),讓我感覺到了一種美感舱馅。

AOP 要實(shí)現(xiàn)的是在我們原來寫的代碼的基礎(chǔ)上,進(jìn)行一定的包裝代嗤,如在方法執(zhí)行前、方法返回后干毅、方法拋出異常后等地方進(jìn)行一定的攔截處理或者叫增強(qiáng)處理。

AOP 的實(shí)現(xiàn)并不是因?yàn)?Java 提供了什么神奇的鉤子硝逢,可以把方法的幾個(gè)生命周期告訴我們绅喉,而是我們要實(shí)現(xiàn)一個(gè)代理叫乌,實(shí)際運(yùn)行的實(shí)例其實(shí)是生成的代理類的實(shí)例

名詞概念

前面提到過革屠,Spring AOP 延用了 AspectJ 中的概念,使用了 AspectJ 提供的 jar 包中的注解屠阻。也就是Spring AOP里面的概念和術(shù)語,并不是Spring獨(dú)有的国觉,而是和AOP相關(guān)的虾啦。

概念可以草草看過,在看了之后的章節(jié)之后再回來看會(huì)對概念理解的更深傲醉。

術(shù)語 概念
Aspect 切面是PointcutAdvice的集合,一般單獨(dú)作為一個(gè)類硬毕。PointcutAdvice共同定義了關(guān)于切面的全部內(nèi)容,它是什么時(shí)候吐咳,在何時(shí)和何處完成功能。
Joinpoint 這表示你的應(yīng)用程序中可以插入AOP方面的一點(diǎn)童谒。也可以說,這是應(yīng)用程序中使用Spring AOP框架采取操作的實(shí)際位置饥伊。
Advice 這是在方法執(zhí)行之前或之后采取的實(shí)際操作。 這是在Spring AOP框架的程序執(zhí)行期間調(diào)用的實(shí)際代碼片段蔫饰。
Pointcut 這是一組一個(gè)或多個(gè)切入點(diǎn),在切點(diǎn)應(yīng)該執(zhí)行Advice篓吁。 您可以使用表達(dá)式或模式指定切入點(diǎn),后面示例會(huì)提到越除。
Introduction 引用允許我們向現(xiàn)有的類添加新的方法或者屬性
Weaving 創(chuàng)建一個(gè)被增強(qiáng)對象的過程。這可以在編譯時(shí)完成(例如使用AspectJ編譯器)翼雀,也可以在運(yùn)行時(shí)完成。Spring和其他純Java AOP框架一樣狼渊,在運(yùn)行時(shí)完成織入类垦。

PS:在整理概念的時(shí)候有個(gè)疑問,為什么網(wǎng)上這么多中文文章把a(bǔ)dvice 翻譯成“通知”呢蚤认??砰琢?概念上說得通嗎?陪汽??我更愿意翻譯成“增強(qiáng)”(并發(fā)中文網(wǎng)ifeve.com 也是翻譯成增強(qiáng))

還有一些注解挚冤,表示Advice的類型,或者說增強(qiáng)的時(shí)機(jī)澳骤,看過之后的示例之后會(huì)更加的清楚。

術(shù)語 概念
Before 在方法被調(diào)用之前執(zhí)行增強(qiáng)
After 在方法被調(diào)用之后執(zhí)行增強(qiáng)
After-returning 在方法成功執(zhí)行之后執(zhí)行增強(qiáng)
After-throwing 在方法拋出指定異常后執(zhí)行增強(qiáng)
Around 在方法調(diào)用的前后執(zhí)行自定義的增強(qiáng)行為(最靈活的方式)

使用方式

Spring 2.0 之后宴凉,Spring AOP有了兩種配置方式表悬。

  1. schema-based:Spring 2.0 以后使用 XML 的方式來配置,使用 命名空間 <aop />

  2. @AspectJ 配置:Spring 2.0 以后提供的注解方式蟆沫。這里雖然叫做 @AspectJ,但是這個(gè)和 AspectJ 其實(shí)沒啥關(guān)系饭庞。

PS:個(gè)人比較鐘情于@AspectJ 這種方式,使用下來是最方面的绸狐。也可能是因?yàn)槲矣X得XML方式配置的Spring Bean很不簡潔卤恳、寫起來不好看吧寒矿,所以有點(diǎn)排斥吧。23333~

本文主要針對注解方式講解拆融,并且給出對應(yīng)的DEMO;之后的源碼解析也會(huì)以注解的這種方式為范例講解Spring AOP的源碼(整個(gè)源碼解析看完镜豹,會(huì)對其他方式觸類旁通,因?yàn)樵矶际且粯拥模?/p>

如果對其他配置方式感興趣的同學(xué)可以google其他的學(xué)習(xí)資料趟脂。


來一條分割線搞旭,正式開始

1. 開啟@AspectJ注解配置方式

開啟@AspectJ的注解配置方式,有兩種方式

  1. 在XML中配置:

    <aop:aspectj-autoproxy/>
    
  2. 使用@EnableAspectJAutoProxy注解

    @Configuration
    @EnableAspectJAutoProxy
    public class Config {
    
    }
    

開啟了上述配置之后肄渗,所有在容器中@AspectJ注解的 bean 都會(huì)被 Spring 當(dāng)做是 AOP 配置類翎嫡,稱為一個(gè) Aspect。

NOTE:這里有個(gè)要注意的地方惑申,@AspectJ 注解只能作用于Spring Bean 上面,所以你用 @Aspect 修飾的類要么是用 @Component注解修飾圈驼,要么是在 XML中配置過的。

比如下面的寫法绩脆,

// 有效的AOP配置類
@Aspect
@Component
public class MyAspect {
    //....   
}

// 如果沒有在XML配置過,那這個(gè)就是無效的AOP配置類
@Aspect
public class MyAspect {
    //....   
}

2. 配置 Pointcut (增強(qiáng)的切入點(diǎn))

Pointcut 在大部分地方被翻譯成切點(diǎn)惕味,用于定義哪些方法需要被增強(qiáng)或者說需要被攔截。

在Spring 中名挥,我們可以認(rèn)為 Pointcut 是用來匹配Spring 容器中所有滿足指定條件的bean的方法主守。

比如下面的寫法榄融,

    // 指定的方法
    @Pointcut("execution(* testExecution(..))")
    public void anyTestMethod() {}

下面完整列舉一下 Pointcut 的匹配方式:

  1. execution:匹配方法簽名

    這個(gè)最簡單的方式就是上面的例子蹋艺,"execution(* testExecution(..))"表示的是匹配名為testExecution的方法,*代表任意返回值捎谨,(..)表示零個(gè)或多個(gè)任意參數(shù)涛救。

  1. within:指定所在類或所在包下面的方法(Spring AOP 獨(dú)有)

        // service 層
        // ".." 代表包及其子包
        @Pointcut("within(ric.study.demo.aop.svc..*)")
        public void inSvcLayer() {}
    
  2. @annotation:方法上具有特定的注解

        // 指定注解
        @Pointcut("@annotation(ric.study.demo.aop.HaveAop)")
        public void withAnnotation() {}
    
  3. bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 獨(dú)有)

        // controller 層
        @Pointcut("bean(testController)")
        public void inControllerLayer() {}
    

上述是日常使用中常見的幾種配置方式

有更細(xì)的匹配需求的,可以參考這篇文章:https://www.baeldung.com/spring-aop-pointcut-tutorial

關(guān)于 Pointcut 的配置检吆,Spring 官方有這么一段建議:

When working with enterprise applications, you often want to refer to modules of the application and particular sets of operations from within several aspects. We recommend defining a "SystemArchitecture" aspect that captures common pointcut expressions for this purpose. A typical such aspect would look as follows:

意思就是程储,如果你是在開發(fā)企業(yè)級應(yīng)用,Spring 建議你使用 SystemArchitecture這種切面配置方式章鲤,即將一些公共的PointCut 配置全部寫在這個(gè)一個(gè)類里面維護(hù)。官網(wǎng)文檔給的例子像下面這樣(它文中使用 XML 配置的败徊,所以沒加@Component注解)

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SystemArchitecture {

  /**
   * A join point is in the web layer if the method is defined
   * in a type in the com.xyz.someapp.web package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.web..*)")
  public void inWebLayer() {}

  /**
   * A join point is in the service layer if the method is defined
   * in a type in the com.xyz.someapp.service package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.service..*)")
  public void inServiceLayer() {}

  /**
   * A join point is in the data access layer if the method is defined
   * in a type in the com.xyz.someapp.dao package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.dao..*)")
  public void inDataAccessLayer() {}

  /**
   * A business service is the execution of any method defined on a service
   * interface. This definition assumes that interfaces are placed in the
   * "service" package, and that implementation types are in sub-packages.
   * 
   * If you group service interfaces by functional area (for example, 
   * in packages com.xyz.someapp.abc.service and com.xyz.def.service) then
   * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
   * could be used instead.
   */
  @Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
  public void businessService() {}
  
  /**
   * A data access operation is the execution of any method defined on a 
   * dao interface. This definition assumes that interfaces are placed in the
   * "dao" package, and that implementation types are in sub-packages.
   */
  @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
  public void dataAccessOperation() {}

}

上面這個(gè) SystemArchitecture 很好理解皱蹦,該 Aspect 定義了一堆的 Pointcut,隨后在任何需要 Pointcut 的地方都可以直接引用沪哺。

配置切點(diǎn),代表著我們想讓程序攔截哪一些方法辜妓,但程序需要怎么對攔截的方法進(jìn)行增強(qiáng),就是后面要介紹的配置 Advice嫌拣。

3. 配置Advice

注意,實(shí)際開發(fā)過程當(dāng)中异逐,Aspect 類應(yīng)該遵守單一職責(zé)原則,不要把所有的Advice配置全部寫在一個(gè)Aspect類里面腥例。

這里是為了演示方便辅甥,所以寫在了一起燎竖。

先直接上示例代碼,里面包含了Advice 的幾種配置方式(上文名詞概念小節(jié)中有提到)夏块。

/**
 * 注:實(shí)際開發(fā)過程當(dāng)中,Advice應(yīng)遵循單一職責(zé)脐供,不應(yīng)混在一起
 *
 * @author Richard_yyf
 * @version 1.0 2019/10/28
 */
@Aspect
@Component
public class GlobalAopAdvice {

    @Before("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ... 實(shí)現(xiàn)代碼
    }

    // 實(shí)際使用過程當(dāng)中 可以像這樣把Advice 和 Pointcut 合在一起,直接在Advice上面定義切入點(diǎn)
    @Before("execution(* ric.study.demo.dao.*.*(..))")
    public void doAccessCheck() {
        // ... 實(shí)現(xiàn)代碼
    }

    // 在方法
    @AfterReturning("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ... 實(shí)現(xiàn)代碼
    }

    // returnVal 就是相應(yīng)方法的返回值
    @AfterReturning(
        pointcut="ric.study.demo.aop.SystemArchitecture.dataAccessOperation()",
        returning="returnVal")
    public void doAccessCheck(Object returnVal) {
        //  ... 實(shí)現(xiàn)代碼
    }

    // 異常返回的時(shí)候
    @AfterThrowing("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ... 實(shí)現(xiàn)代碼
    }

    // 注意理解它和 @AfterReturning 之間的區(qū)別政己,這里會(huì)攔截正常返回和異常的情況
    @After("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // 通常就像 finally 塊一樣使用掏愁,用來釋放資源。
        // 無論正常返回還是異常退出果港,都會(huì)被攔截到
    }

    // 這種最靈活,既能做 @Before 的事情赦肃,也可以做 @AfterReturning 的事情
    @Around("ric.study.demo.aop.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        //  target 方法執(zhí)行前... 實(shí)現(xiàn)代碼
        Object retVal = pjp.proceed();
        //  target 方法執(zhí)行后... 實(shí)現(xiàn)代碼
        return retVal;
    }
}

在某些場景下,我們想在@Before的時(shí)候他宛,去獲取方法的入?yún)ⅲ热邕M(jìn)行一些日志的記錄厅各,我們可以通過 org.aspectj.lang.JoinPoint來實(shí)現(xiàn)。上文中的ProceedingJoinPoint就是其子類队塘。

@Before("...")
public void logArgs(JoinPoint joinPoint) {
    System.out.println("方法執(zhí)行前宜鸯,打印入?yún)ⅲ? + Arrays.toString(joinPoint.getArgs()));
}

再舉個(gè)與之對應(yīng)的,方法返參打恿苄洹:

@AfterReturning( pointcut="...", returning="returnVal")
public void logReturnVal(Object returnVal) {
    System.out.println("方法執(zhí)行后,打印返參:" + returnVal));
}

快速Demo

介紹完上述的配置過程之后,我們用一個(gè)快速的Demo來實(shí)際演示一遍陌凳。這里把順序變一下;

1. 編寫 目標(biāo)類

package ric.study.demo.aop.svc;

public interface TestSvc {

    void process();
}

@Service("testSvc")
public class TestSvcImpl implements TestSvc {
    @Override
    public void process() {
        System.out.println("test svc is working");
    }
}

public interface DateSvc {

    void printDate(Date date);
}

@Service("dateSvc")
public class DateSvcImpl implements DateSvc {

    @Override
    public void printDate(Date date) {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
    }
}

2. 配置 Pointcut

@Aspect
@Component
public class PointCutConfig {
    @Pointcut("within(ric.study.demo.aop.svc..*)")
    public void inSvcLayer() {}   
}

3. 配置Advice

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/29
 */
@Component
@Aspect
public class ServiceLogAspect {

    // 攔截合敦,打印日志验游,并且通過JoinPoint 獲取方法參數(shù)
    @Before("ric.study.demo.aop.PointCutConfig.inSvcLayer()")
    public void logBeforeSvc(JoinPoint joinPoint) {
        System.out.println("在service 方法執(zhí)行前 打印第 1 次日志");
        System.out.println("攔截的service 方法的方法簽名: " + joinPoint.getSignature());
        System.out.println("攔截的service 方法的方法入?yún)? " + Arrays.toString(joinPoint.getArgs()));
    }

    // 這里是Advice和Pointcut 合在一起配置的方式
    @Before("within(ric.study.demo.aop.svc..*)")
    public void logBeforeSvc2() {
        System.out.println("在service的方法執(zhí)行前 打印第 2 次日志");
    }
}

4. 開啟@AspectJ注解配置方式,并啟動(dòng)

這里為了圖方便批狱,把配置類和啟動(dòng)類寫在了一起,

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/28
 */
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("ric.study.demo.aop")
public class Boostrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
        TestSvc svc = (TestSvc) context.getBean("testSvc");
        svc.process();
        System.out.println("==================");
        DateSvc dateSvc = (DateSvc) context.getBean("dateSvc");
        dateSvc.printDate(new Date());
    }
}

5. 輸出

在service 方法執(zhí)行前 打印第 1 次日志
攔截的service 方法的方法簽名: void ric.study.demo.aop.svc.TestSvcImpl.process()
攔截的service 方法的方法入?yún)? []
在service的方法執(zhí)行前 打印第 2 次日志
test svc is working
==================
在service 方法執(zhí)行前 打印第 1 次日志
攔截的service 方法的方法簽名: void ric.study.demo.aop.svc.DateSvcImpl.printDate(Date)
攔截的service 方法的方法入?yún)? [Mon Nov 04 18:11:34 CST 2019]
在service的方法執(zhí)行前 打印第 2 次日志
2019-11-04 18:11:34

JDK 動(dòng)態(tài)代理和 Cglib

前面有提到過,Spring AOP在目標(biāo)類有實(shí)現(xiàn)接口的時(shí)候盐肃,會(huì)使用JDK 動(dòng)態(tài)代理來生成代理類,我們結(jié)合上面的DEMO看看砸王,

image.png

如果我們想不管是否有實(shí)現(xiàn)接口,都是強(qiáng)制使用Cglib的方式來實(shí)現(xiàn)怎么辦谦铃?

Spring 提供給了我們對應(yīng)的配置方式,也就是proxy-target-class.

注解方式:
//@EnableAspectJAutoProxy(proxyTargetClass = true) // 這樣子就是默認(rèn)使用CGLIB
XML方式:
<aop:config proxy-target-class="true">

改了之后驹闰,

image.png

小結(jié)

本文詳細(xì)介紹了Spring AOP的起源、名詞概念以及基于注解的使用方式嘹朗。

本文按照作者的寫作習(xí)慣,是源碼解析章節(jié)的前置學(xué)習(xí)章節(jié)屹培。在下一章中,我們會(huì)以注解方式為入口褪秀,介紹Spring AOP 的源碼設(shè)計(jì),解讀相關(guān)核心源碼(整個(gè)源碼解析看完媒吗,會(huì)對其他方式觸類旁通,因?yàn)樵矶际且粯拥模?/p>

感興趣的可以翻到【前言】部分蝴猪,再看一下思維導(dǎo)圖膊爪。

如果本文有幫助到你嚎莉,希望能點(diǎn)個(gè)贊,這是對我的最大動(dòng)力趋箩。

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叫确,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子竹勉,更是在濱河造成了極大的恐慌,老刑警劉巖次乓,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異城看,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)测柠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轰胁,“玉大人,你說我怎么就攤上這事软吐。” “怎么了吟税?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肖抱。 經(jīng)常有香客問我,道長意述,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任荤崇,我火速辦了婚禮,結(jié)果婚禮上术荤,老公的妹妹穿的比我還像新娘倚喂。我一直安慰自己瓣戚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布子库。 她就那樣靜靜地躺著,像睡著了一般仑嗅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仓技,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼拯辙。 笑死郭变,一個(gè)胖子當(dāng)著我的面吹牛诉濒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夕春,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼片排!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起率寡,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤倚搬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捅僵,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年上荡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片榛臼。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沛善,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情金刁,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布尤蛮,位于F島的核電站,受9級特大地震影響产捞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坯临,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一恋昼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧液肌,春花似錦、人聲如沸嗦哆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至烁峭,卻和暖如春秕铛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缩挑。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留供置,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓紧阔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親续担。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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