徹底征服 Spring AOP 之 理論篇

基本知識(shí)

其實(shí), 接觸了這么久的 AOP, 我感覺(jué), AOP 給人難以理解的一個(gè)關(guān)鍵點(diǎn)是它的概念比較多, 而且坑爹的是, 這些概念經(jīng)過(guò)了中文翻譯后, 變得面目全非, 相同的一個(gè)術(shù)語(yǔ), 在不同的翻譯下, 含義總有著各種莫名其妙的差別. 鑒于此, 我在本章的開(kāi)頭, 著重為為大家介紹一個(gè) Spring AOP 的各項(xiàng)術(shù)語(yǔ)的基本含義. 為了術(shù)語(yǔ)傳達(dá)的準(zhǔn)確性, 我在接下來(lái)的敘述中, 能使用英文術(shù)語(yǔ)的地方, 盡量使用英文.

什么是 AOP

AOP(Aspect-Oriented Programming), 即 面向切面編程, 它與 OOP( Object-Oriented Programming, 面向?qū)ο缶幊? 相輔相成, 提供了與 OOP 不同的抽象軟件結(jié)構(gòu)的視角.
在 OOP 中, 我們以類(class)作為我們的基本單元, 而 AOP 中的基本單元是 Aspect(切面)

術(shù)語(yǔ)

Aspect(切面)

aspectpointcountadvice 組成, 它既包含了橫切邏輯的定義, 也包括了連接點(diǎn)的定義. Spring AOP就是負(fù)責(zé)實(shí)施切面的框架, 它將切面所定義的橫切邏輯織入到切面所指定的連接點(diǎn)中.
AOP的工作重心在于如何將增強(qiáng)織入目標(biāo)對(duì)象的連接點(diǎn)上, 這里包含兩個(gè)工作:

  1. 如何通過(guò) pointcut 和 advice 定位到特定的 joinpoint 上
  2. 如何在 advice 中編寫(xiě)切面代碼.

可以簡(jiǎn)單地認(rèn)為, 使用 @Aspect 注解的類就是切面.

advice(增強(qiáng))

由 aspect 添加到特定的 join point(即滿足 point cut 規(guī)則的 join point) 的一段代碼.
許多 AOP框架, 包括 Spring AOP, 會(huì)將 advice 模擬為一個(gè)攔截器(interceptor), 并且在 join point 上維護(hù)多個(gè) advice, 進(jìn)行層層攔截.
例如 HTTP 鑒權(quán)的實(shí)現(xiàn), 我們可以為每個(gè)使用 RequestMapping 標(biāo)注的方法織入 advice, 當(dāng) HTTP 請(qǐng)求到來(lái)時(shí), 首先進(jìn)入到 advice 代碼中, 在這里我們可以分析這個(gè) HTTP 請(qǐng)求是否有相應(yīng)的權(quán)限, 如果有, 則執(zhí)行 Controller, 如果沒(méi)有, 則拋出異常. 這里的 advice 就扮演著鑒權(quán)攔截器的角色了.

連接點(diǎn)(join point)

a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

程序運(yùn)行中的一些時(shí)間點(diǎn), 例如一個(gè)方法的執(zhí)行, 或者是一個(gè)異常的處理.
在 Spring AOP 中, join point 總是方法的執(zhí)行點(diǎn), 即只有方法連接點(diǎn).

切點(diǎn)(point cut)

匹配 join point 的謂詞(a predicate that matches join points).
Advice 是和特定的 point cut 關(guān)聯(lián)的, 并且在 point cut 相匹配的 join point 中執(zhí)行.
在 Spring 中, 所有的方法都可以認(rèn)為是 joinpoint, 但是我們并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一組規(guī)則(使用 AspectJ pointcut expression language 來(lái)描述) 來(lái)匹配joinpoint, 給滿足規(guī)則的 joinpoint 添加 Advice.

關(guān)于join point 和 point cut 的區(qū)別

在 Spring AOP 中, 所有的方法執(zhí)行都是 join point. 而 point cut 是一個(gè)描述信息, 它修飾的是 join point, 通過(guò) point cut, 我們就可以確定哪些 join point 可以被織入 Advice. 因此 join point 和 point cut 本質(zhì)上就是兩個(gè)不同緯度上的東西.
advice 是在 join point 上執(zhí)行的, 而 point cut 規(guī)定了哪些 join point 可以執(zhí)行哪些 advice

introduction

為一個(gè)類型添加額外的方法或字段. Spring AOP 允許我們?yōu)?目標(biāo)對(duì)象 引入新的接口(和對(duì)應(yīng)的實(shí)現(xiàn)). 例如我們可以使用 introduction 來(lái)為一個(gè) bean 實(shí)現(xiàn) IsModified 接口, 并以此來(lái)簡(jiǎn)化 caching 的實(shí)現(xiàn).

目標(biāo)對(duì)象(Target)

織入 advice 的目標(biāo)對(duì)象. 目標(biāo)對(duì)象也被稱為 advised object.
因?yàn)?Spring AOP 使用運(yùn)行時(shí)代理的方式來(lái)實(shí)現(xiàn) aspect, 因此 adviced object 總是一個(gè)代理對(duì)象(proxied object)
注意, adviced object 指的不是原來(lái)的類, 而是織入 advice 后所產(chǎn)生的代理類.

AOP proxy

一個(gè)類被 AOP 織入 advice, 就會(huì)產(chǎn)生一個(gè)結(jié)果類, 它是融合了原類和增強(qiáng)邏輯的代理類.
在 Spring AOP 中, 一個(gè) AOP 代理是一個(gè) JDK 動(dòng)態(tài)代理對(duì)象或 CGLIB 代理對(duì)象.

織入(Weaving)

將 aspect 和其他對(duì)象連接起來(lái), 并創(chuàng)建 adviced object 的過(guò)程.
根據(jù)不同的實(shí)現(xiàn)技術(shù), AOP織入有三種方式:

  • 編譯器織入, 這要求有特殊的Java編譯器.
  • 類裝載期織入, 這需要有特殊的類裝載器.
  • 動(dòng)態(tài)代理織入, 在運(yùn)行期為目標(biāo)類添加增強(qiáng)(Advice)生成子類的方式.
    Spring 采用動(dòng)態(tài)代理織入, 而AspectJ采用編譯器織入和類裝載期織入.

advice 的類型

  • before advice, 在 join point 前被執(zhí)行的 advice. 雖然 before advice 是在 join point 前被執(zhí)行, 但是它并不能夠阻止 join point 的執(zhí)行, 除非發(fā)生了異常(即我們?cè)?before advice 代碼中, 不能人為地決定是否繼續(xù)執(zhí)行 join point 中的代碼)
  • after return advice, 在一個(gè) join point 正常返回后執(zhí)行的 advice
  • after throwing advice, 當(dāng)一個(gè) join point 拋出異常后執(zhí)行的 advice
  • after(final) advice, 無(wú)論一個(gè) join point 是正常退出還是發(fā)生了異常, 都會(huì)被執(zhí)行的 advice.
  • around advice, 在 join point 前和 joint point 退出后都執(zhí)行的 advice. 這個(gè)是最常用的 advice.

關(guān)于 AOP Proxy

Spring AOP 默認(rèn)使用標(biāo)準(zhǔn)的 JDK 動(dòng)態(tài)代理(dynamic proxy)技術(shù)來(lái)實(shí)現(xiàn) AOP 代理, 通過(guò)它, 我們可以為任意的接口實(shí)現(xiàn)代理.
如果需要為一個(gè)類實(shí)現(xiàn)代理, 那么可以使用 CGLIB 代理. 當(dāng)一個(gè)業(yè)務(wù)邏輯對(duì)象沒(méi)有實(shí)現(xiàn)接口時(shí), 那么Spring AOP 就默認(rèn)使用 CGLIB 來(lái)作為 AOP 代理了. 即如果我們需要為一個(gè)方法織入 advice, 但是這個(gè)方法不是一個(gè)接口所提供的方法, 則此時(shí) Spring AOP 會(huì)使用 CGLIB 來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理. 鑒于此, Spring AOP 建議基于接口編程, 對(duì)接口進(jìn)行 AOP 而不是類.

徹底理解 aspect, join point, point cut, advice

看完了上面的理論部分知識(shí), 我相信還是會(huì)有不少朋友感覺(jué)到 AOP 的概念還是很模糊, 對(duì) AOP 中的各種概念理解的還不是很透徹. 其實(shí)這很正常, 因?yàn)?AOP 中的概念是在是太多了, 我當(dāng)時(shí)也是花了老大勁才梳理清楚的.
下面我以一個(gè)簡(jiǎn)單的例子來(lái)比喻一下 AOP 中 aspect, jointpoint, pointcut 與 advice 之間的關(guān)系.

讓我們來(lái)假設(shè)一下, 從前有一個(gè)叫爪哇的小縣城, 在一個(gè)月黑風(fēng)高的晚上, 這個(gè)縣城中發(fā)生了命案. 作案的兇手十分狡猾, 現(xiàn)場(chǎng)沒(méi)有留下什么有價(jià)值的線索. 不過(guò)萬(wàn)幸的是, 剛從隔壁回來(lái)的老王恰好在這時(shí)候無(wú)意中發(fā)現(xiàn)了兇手行兇的過(guò)程, 但是由于天色已晚, 加上兇手蒙著面, 老王并沒(méi)有看清兇手的面目, 只知道兇手是個(gè)男性, 身高約七尺五寸. 爪哇縣的縣令根據(jù)老王的描述, 對(duì)守門(mén)的士兵下命令說(shuō): 凡是發(fā)現(xiàn)有身高七尺五寸的男性, 都要抓過(guò)來(lái)審問(wèn). 士兵當(dāng)然不敢違背縣令的命令, 只好把進(jìn)出城的所有符合條件的人都抓了起來(lái).

來(lái)讓我們看一下上面的一個(gè)小故事和 AOP 到底有什么對(duì)應(yīng)關(guān)系.
首先我們知道, 在 Spring AOP 中 join point 指代的是所有方法的執(zhí)行點(diǎn), 而 point cut 是一個(gè)描述信息, 它修飾的是 join point, 通過(guò) point cut, 我們就可以確定哪些 join point 可以被織入 Advice. 對(duì)應(yīng)到我們?cè)谏厦媾e的例子, 我們可以做一個(gè)簡(jiǎn)單的類比, join point 就相當(dāng)于 爪哇的小縣城里的百姓, point cut 就相當(dāng)于 老王所做的指控, 即兇手是個(gè)男性, 身高約七尺五寸, 而 advice 則是施加在符合老王所描述的嫌疑人的動(dòng)作: 抓過(guò)來(lái)審問(wèn).
為什么可以這樣類比呢?

  • join point --> 爪哇的小縣城里的百姓: 因?yàn)楦鶕?jù)定義, join point 是所有可能被織入 advice 的候選的點(diǎn), 在 Spring AOP中, 則可以認(rèn)為所有方法執(zhí)行點(diǎn)都是 join point. 而在我們上面的例子中, 命案發(fā)生在小縣城中, 按理說(shuō)在此縣城中的所有人都有可能是嫌疑人.
  • point cut --> 男性, 身高約七尺五寸: 我們知道, 所有的方法(joint point) 都可以織入 advice, 但是我們并不希望在所有方法上都織入 advice, 而 pointcut 的作用就是提供一組規(guī)則來(lái)匹配joinpoint, 給滿足規(guī)則的 joinpoint 添加 advice. 同理, 對(duì)于縣令來(lái)說(shuō), 他再昏庸, 也知道不能把縣城中的所有百姓都抓起來(lái)審問(wèn), 而是根據(jù)兇手是個(gè)男性, 身高約七尺五寸, 把符合條件的人抓起來(lái). 在這里 兇手是個(gè)男性, 身高約七尺五寸 就是一個(gè)修飾謂語(yǔ), 它限定了兇手的范圍, 滿足此修飾規(guī)則的百姓都是嫌疑人, 都需要抓起來(lái)審問(wèn).
  • advice --> 抓過(guò)來(lái)審問(wèn), advice 是一個(gè)動(dòng)作, 即一段 Java 代碼, 這段 Java 代碼是作用于 point cut 所限定的那些 join point 上的. 同理, 對(duì)比到我們的例子中, 抓過(guò)來(lái)審問(wèn) 這個(gè)動(dòng)作就是對(duì)作用于那些滿足 男性, 身高約七尺五寸爪哇的小縣城里的百姓.
  • aspect: aspect 是 point cut 與 advice 的組合, 因此在這里我們就可以類比: "根據(jù)老王的線索, 凡是發(fā)現(xiàn)有身高七尺五寸的男性, 都要抓過(guò)來(lái)審問(wèn)" 這一整個(gè)動(dòng)作可以被認(rèn)為是一個(gè) aspect.

或則我們也可以從語(yǔ)法的角度來(lái)簡(jiǎn)單類比一下. 我們?cè)趯W(xué)英語(yǔ)時(shí), 經(jīng)常會(huì)接觸什么 定語(yǔ), 被動(dòng)句 之類的概念, 那么可以做一個(gè)不嚴(yán)謹(jǐn)?shù)念惐? 即 joinpoint 可以認(rèn)為是一個(gè) 賓語(yǔ), 而 pointcut 則可以類比為修飾 joinpoint 的定語(yǔ), 那么整個(gè) aspect 就可以描述為: 滿足 pointcut 規(guī)則的 joinpoint 會(huì)被添加相應(yīng)的 advice 操作.

@AspectJ 支持

@AspectJ 是一種使用 Java 注解來(lái)實(shí)現(xiàn) AOP 的編碼風(fēng)格.
@AspectJ 風(fēng)格的 AOP 是 AspectJ Project 在 AspectJ 5 中引入的, 并且 Spring 也支持@AspectJ 的 AOP 風(fēng)格.

使能 @AspectJ 支持

@AspectJ 可以以 XML 的方式或以注解的方式來(lái)使能, 并且不論以哪種方式使能@ASpectJ, 我們都必須保證 aspectjweaver.jar 在 classpath 中.

使用 Java Configuration 方式使能@AspectJ

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

使用 XML 方式使能@AspectJ

<aop:aspectj-autoproxy/>

定義 aspect(切面)

當(dāng)使用注解 @Aspect 標(biāo)注一個(gè) Bean 后, 那么 Spring 框架會(huì)自動(dòng)收集這些 Bean, 并添加到 Spring AOP 中, 例如:

@Component
@Aspect
public class MyTest {
}

注意, 僅僅使用@Aspect 注解, 并不能將一個(gè) Java 對(duì)象轉(zhuǎn)換為 Bean, 因此我們還需要使用類似 @Component 之類的注解.
注意, 如果一個(gè) 類被@Aspect 標(biāo)注, 則這個(gè)類就不能是其他 aspect 的 **advised object** 了, 因?yàn)槭褂?@Aspect 后, 這個(gè)類就會(huì)被排除在 auto-proxying 機(jī)制之外.

聲明 pointcut

一個(gè) pointcut 的聲明由兩部分組成:

  • 一個(gè)方法簽名, 包括方法名和相關(guān)參數(shù)
  • 一個(gè) pointcut 表達(dá)式, 用來(lái)指定哪些方法執(zhí)行是我們感興趣的(即因此可以織入 advice).

在@AspectJ 風(fēng)格的 AOP 中, 我們使用一個(gè)方法來(lái)描述 pointcut, 即:

@Pointcut("execution(* com.xys.service.UserService.*(..))") // 切點(diǎn)表達(dá)式
private void dataAccessOperation() {} // 切點(diǎn)前面

這個(gè)方法必須無(wú)返回值.
這個(gè)方法本身就是 pointcut signature, pointcut 表達(dá)式使用@Pointcut 注解指定.
上面我們簡(jiǎn)單地定義了一個(gè) pointcut, 這個(gè) pointcut 所描述的是: 匹配所有在包 com.xys.service.UserService 下的所有方法的執(zhí)行.

切點(diǎn)標(biāo)志符(designator)

AspectJ5 的切點(diǎn)表達(dá)式由標(biāo)志符(designator)和操作參數(shù)組成. 如 "execution(* greetTo(..))" 的切點(diǎn)表達(dá)式, execution 就是 標(biāo)志符, 而圓括號(hào)里的 *** greetTo(..)** 就是操作參數(shù)

execution

匹配 join point 的執(zhí)行, 例如 "execution(* hello(..))" 表示匹配所有目標(biāo)類中的 hello() 方法. 這個(gè)是最基本的 pointcut 標(biāo)志符.

within

匹配特定包下的所有 join point, 例如 within(com.xys.*) 表示 com.xys 包中的所有連接點(diǎn), 即包中的所有類的所有方法. 而 within(com.xys.service.*Service) 表示在 com.xys.service 包中所有以 Service 結(jié)尾的類的所有的連接點(diǎn).

this 與 target

this 的作用是匹配一個(gè) bean, 這個(gè) bean(Spring AOP proxy) 是一個(gè)給定類型的實(shí)例(instance of). 而 target 匹配的是一個(gè)目標(biāo)對(duì)象(target object, 即需要織入 advice 的原始的類), 此對(duì)象是一個(gè)給定類型的實(shí)例(instance of).

bean

匹配 bean 名字為指定值的 bean 下的所有方法, 例如:

bean(*Service) // 匹配名字后綴為 Service 的 bean 下的所有方法
bean(myService) // 匹配名字為 myService 的 bean 下的所有方法
args

匹配參數(shù)滿足要求的的方法.
例如:

@Pointcut("within(com.xys.demo2.*)")
public void pointcut2() {
}

@Before(value = "pointcut2()  &&  args(name)")
public void doSomething(String name) {
    logger.info("---page: {}---", name);
}
@Service
public class NormalService {
    private Logger logger = LoggerFactory.getLogger(getClass());

    public void someMethod() {
        logger.info("---NormalService: someMethod invoked---");
    }


    public String test(String name) {
        logger.info("---NormalService: test invoked---");
        return "服務(wù)一切正常";
    }
}

當(dāng) NormalService.test 執(zhí)行時(shí), 則 advice doSomething 就會(huì)執(zhí)行, test 方法的參數(shù) name 就會(huì)傳遞到 doSomething 中.

常用例子:

// 匹配只有一個(gè)參數(shù) name 的方法
@Before(value = "aspectMethod()  &&  args(name)")
public void doSomething(String name) {
}

// 匹配第一個(gè)參數(shù)為 name 的方法
@Before(value = "aspectMethod()  &&  args(name, ..)")
public void doSomething(String name) {
}

// 匹配第二個(gè)參數(shù)為 name 的方法
Before(value = "aspectMethod()  &&  args(*, name, ..)")
public void doSomething(String name) {
}
@annotation

匹配由指定注解所標(biāo)注的方法, 例如:

@Pointcut("@annotation(com.xys.demo1.AuthChecker)")
public void pointcut() {
}

則匹配由注解 AuthChecker 所標(biāo)注的方法.

常見(jiàn)的切點(diǎn)表達(dá)式

匹配方法簽名
// 匹配指定包中的所有的方法
execution(* com.xys.service.*(..))

// 匹配當(dāng)前包中的指定類的所有方法
execution(* UserService.*(..))

// 匹配指定包中的所有 public 方法
execution(public * com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 并且返回值是 int 類型的方法
execution(public int com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 并且第一個(gè)參數(shù)是 String, 返回值是 int 類型的方法
execution(public int com.xys.service.*(String name, ..))
匹配類型簽名
// 匹配指定包中的所有的方法, 但不包括子包
within(com.xys.service.*)

// 匹配指定包中的所有的方法, 包括子包
within(com.xys.service..*)

// 匹配當(dāng)前包中的指定類中的方法
within(UserService)


// 匹配一個(gè)接口的所有實(shí)現(xiàn)類中的實(shí)現(xiàn)的方法
within(UserDao+)
匹配 Bean 名字
// 匹配以指定名字結(jié)尾的 Bean 中的所有方法
bean(*Service)
切點(diǎn)表達(dá)式組合
// 匹配以 Service 或 ServiceImpl 結(jié)尾的 bean
bean(*Service || *ServiceImpl)

// 匹配名字以 Service 開(kāi)頭, 并且在包 com.xys.service 中的 bean
bean(*Service) && within(com.xys.service.*)

聲明 advice

advice 是和一個(gè) pointcut 表達(dá)式關(guān)聯(lián)在一起的, 并且會(huì)在匹配的 join point 的方法執(zhí)行的前/后/周?chē)?運(yùn)行. pointcut 表達(dá)式可以是簡(jiǎn)單的一個(gè) pointcut 名字的引用, 或者是完整的 pointcut 表達(dá)式.
下面我們以幾個(gè)簡(jiǎn)單的 advice 為例子, 來(lái)看一下一個(gè) advice 是如何聲明的.

Before advice

/**
 * @author xiongyongshun
 * @version 1.0
 * @created 16/9/9 13:13
 */
@Component
@Aspect
public class BeforeAspectTest {
    // 定義一個(gè) Pointcut, 使用 切點(diǎn)表達(dá)式函數(shù) 來(lái)描述對(duì)哪些 Join point 使用 advise.
    @Pointcut("execution(* com.xys.service.UserService.*(..))")
    public void dataAccessOperation() {
    }
}
@Component
@Aspect
public class AdviseDefine {
    // 定義 advise
    @Before("com.xys.aspect.PointcutDefine.dataAccessOperation()")
    public void doBeforeAccessCheck(JoinPoint joinPoint) {
        System.out.println("*****Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
    }
}

這里, @Before 引用了一個(gè) pointcut, 即 "com.xys.aspect.PointcutDefine.dataAccessOperation()" 是一個(gè) pointcut 的名字.
如果我們?cè)?advice 在內(nèi)置 pointcut, 則可以:

@Component
@Aspect
public class AdviseDefine {
    // 將 pointcut 和 advice 同時(shí)定義
    @Before("within(com.xys.service..*)")
    public void doAccessCheck(JoinPoint joinPoint) {
        System.out.println("*****doAccessCheck, Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
    }
}

around advice

around advice 比較特別, 它可以在一個(gè)方法的之前之前和之后添加不同的操作, 并且甚至可以決定何時(shí), 如何, 是否調(diào)用匹配到的方法.

@Component
@Aspect
public class AdviseDefine {
    // 定義 advise
    @Around("com.xys.aspect.PointcutDefine.dataAccessOperation()")
    public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 開(kāi)始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 結(jié)束
        System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis());
        return retVal;
    }
}

around advice 和前面的 before advice 查不到, 只是我們把注解 @Before 改為了 @Around 了.

下一小節(jié)徹底征服 Spring AOP 之 實(shí)戰(zhàn)篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宇色,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荧关,更是在濱河造成了極大的恐慌咒程,老刑警劉巖被济,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡帚呼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)皱蹦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)煤杀,“玉大人,你說(shuō)我怎么就攤上這事沪哺∩蜃裕” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵辜妓,是天一觀的道長(zhǎng)枯途。 經(jīng)常有香客問(wèn)我,道長(zhǎng)籍滴,這世上最難降的妖魔是什么酪夷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮孽惰,結(jié)果婚禮上晚岭,老公的妹妹穿的比我還像新娘。我一直安慰自己勋功,他們只是感情好坦报,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布库说。 她就那樣靜靜地躺著,像睡著了一般片择。 火紅的嫁衣襯著肌膚如雪潜的。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天字管,我揣著相機(jī)與錄音夏块,去河邊找鬼。 笑死纤掸,一個(gè)胖子當(dāng)著我的面吹牛脐供,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播借跪,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼政己,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了掏愁?” 一聲冷哼從身側(cè)響起歇由,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎果港,沒(méi)想到半個(gè)月后沦泌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辛掠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年谢谦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萝衩。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡回挽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猩谊,到底是詐尸還是另有隱情千劈,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布牌捷,位于F島的核電站墙牌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏暗甥。R本人自食惡果不足惜喜滨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淋袖。 院中可真熱鬧鸿市,春花似錦锯梁、人聲如沸即碗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剥懒。三九已至内舟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間初橘,已是汗流浹背验游。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留保檐,地道東北人耕蝉。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像夜只,于是被迫代替她去往敵國(guó)和親垒在。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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