SpringBoot(一)自動(dòng)裝配基礎(chǔ)

前言

????????最近在學(xué)習(xí)Spring Boot相關(guān)的課程锯茄,過程中以筆記的形式記錄下來鹃彻,方便以后回憶,同時(shí)也在這里和大家探討探討筒占,文章中有漏的或者有補(bǔ)充的酒甸、錯(cuò)誤的都希望大家能夠及時(shí)提出來,本人在此先謝謝了赋铝!

開始之前呢,希望大家?guī)е鴰讉€(gè)問題去學(xué)習(xí):
1沽瘦、Spring注解驅(qū)動(dòng)是什么革骨?
2农尖、這個(gè)功能在什么時(shí)代背景下產(chǎn)生的?
3良哲、這個(gè)功能有什么用盛卡?
4、怎么實(shí)現(xiàn)的筑凫?
5滑沧、優(yōu)點(diǎn)和缺點(diǎn)是什么?
6巍实、這個(gè)功能能應(yīng)用在工作中滓技?
這是對(duì)自我的提問,我認(rèn)為帶著問題去學(xué)習(xí)棚潦,是一種更好的學(xué)習(xí)方式令漂,有利于加深理解。好了丸边,接下來進(jìn)入主題叠必。

(一)起源

????????我們先來簡單的聊聊Spring注解的發(fā)展史。Spring1.x時(shí)代妹窖,那時(shí)候注解的概念剛剛興起纬朝,僅支持如 @Transactional 等注解。到了2.x時(shí)代Spring的注解體系有了雛形骄呼,引入了 @Autowired 共苛、 @Controller這一系列骨架式的注解。3.x是黃金時(shí)代谒麦,它除了引入 @Enable 模塊驅(qū)動(dòng)概念俄讹,加快了Spring注解體系的成型,還引入了配置類 @Configuration@ComponentScan 绕德,使我們可以拋棄XML配置文件的形式患膛,全面擁抱Spring注解,但Spring并未完全放棄XML配置文件耻蛇,它提供了 @ImportResource 允許導(dǎo)入遺留的XML配置文件踪蹬。此外還提供了 @Import允許導(dǎo)入一個(gè)或多個(gè)Java類成為Spring Bean。4.X則趨于完善臣咖,引入了條件化注解 @Conditional 跃捣,使裝配更加的靈活。當(dāng)下是5.X時(shí)代夺蛇,是SpringBoot2.0的底層核心框架疚漆,目前來看,變化不是很大,但也引入了一個(gè) @Indexed 注解娶聘,主要是用來提升啟動(dòng)性能的闻镶。好了,以上是Spring注解的發(fā)展史丸升,接下來我們對(duì)Spring注解體系的幾個(gè)議題進(jìn)行講解铆农。

(二)Spring 模式注解

????????
模式注解是一種用于聲明在應(yīng)用中扮演“組件”角色的注解。如 Spring 中的 @Repository 是用于扮演倉儲(chǔ)角色的模式注解狡耻,用來管理和存儲(chǔ)某種領(lǐng)域?qū)ο蠖掌省_€有如@Component 是通用組件模式、@Service 是服務(wù)模式夷狰、@Configuration 是配置模式等岭皂。其中@Component 作為一種由 Spring 容器托管的通用模式組件,任何被 @Component標(biāo)注的組件均為組件掃描的候選對(duì)象孵淘。類似地蒲障,凡是被 @Component 標(biāo)注的注解,如@Service瘫证,當(dāng)任何組件標(biāo)注它時(shí)揉阎,也被視作組件掃描的候選對(duì)象。
舉例:

Spring注解 場(chǎng)景說明 起始版本
@Componnt 通用組件模式注解 2.5
@Repository 數(shù)據(jù)倉儲(chǔ)模式注解 2.0
@Service 服務(wù)模式注解 2.5
@Controller Web 控制器模式注解 2.5
@Configuration 配置類模式注解 3.0

那么背捌,被這些注解標(biāo)注的類如何交由Spring來管理呢毙籽,或者說如何被Spring所裝配呢?接下來我們就來看看Spring的兩種裝配方式毡庆。

1坑赡、裝配方式

  • <context:component-scan> 方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd ">

    <context:component-scan base-package="com.loong.spring.boot" />
</beans>

第一種是XML配置文件的方式,通過 base-package 這個(gè)屬性指定掃描某個(gè)范圍內(nèi)所有被 @Component 或者其派生注解標(biāo)記的類(Class)么抗,將它們注冊(cè)為 Spring Bean毅否。

我們都知道XML Schema 規(guī)范,標(biāo)簽需要顯示地關(guān)聯(lián)命名空間蝇刀,如配置文件中的
xmlns:context="http://www.springframework.org/schema/context" 螟加,且需要與其處理類建立映射關(guān)系,而該關(guān)系維護(hù)在相對(duì)于 classpath 下的/META-INF/spring.handlers 文件中吞琐。如下:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

可以看到捆探, context 所對(duì)應(yīng)的處理器為 ContextNamespaceHandler

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        .....
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        .....
    }
}

這里當(dāng)Spring啟動(dòng)時(shí),init方法被調(diào)用站粟,隨后注冊(cè)該命名空間下的所有 Bean 定義解析器黍图,可以看到 <context:component-scan />的解析器為 ComponentScanBeanDefinitionParser。具體的處理過程就在此類中奴烙,感興趣的同學(xué)可以去深入了解助被,這里不再贅述剖张。

  • @ComponentScan 方式
@ComponentScan(basePackages = "com.loong.spring.boot")
public class SpringConfiguration {

}

第二種是注解的形式,同樣也是依靠 basePackages 屬性指定掃描范圍恰起。

Spring 在啟動(dòng)時(shí)修械,會(huì)在某個(gè)生命周期內(nèi)創(chuàng)建所有的配置類注解解析器,而 @ComponentScan的處理器為 ComponentScanAnnotationParser检盼,感興趣的同學(xué)可以去深入了解,這里同樣不再贅述翘单。

2吨枉、派生性

我們用自定義注解的方式來看一看文中提到的派生性:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repository
public @interface FirstLevelRepository {
    String value() default "";
} 

可以看到我們自定義了一個(gè) @FirstLevelRepository注解,當(dāng)前注解又標(biāo)注了@Repository哄芜,而 @Repository 又標(biāo)注了 @Component并且注解屬性一致(String value() default"")貌亭,那么就可以表示當(dāng)前注解包含了 @Repository@Component的功能。

派生性其實(shí)可以分為多層次的认臊,如
@SprintBootApplication -> @SpringBootConfiguration -> @Configuration -> @Component
可以看到@Component被派生了多個(gè)層次圃庭,但這種多層次的派生性Spring 4.0版本才開始支持,Spring3.0僅支持兩層失晴。

(三)Spring @Enable 模塊驅(qū)動(dòng)

????????前文提到Spring3.X是一個(gè)黃金時(shí)代剧腻,它不僅全面擁抱注解模式,還開始支持“@Enable模塊驅(qū)動(dòng)”涂屁。所謂“模塊”是指具備相同領(lǐng)域的功能組件集合书在,組合所形成的一個(gè)獨(dú)立的單元,比如 Web MVC 模塊拆又、AspectJ代理模塊儒旬、Caching(緩存)模塊、JMX(Java 管理擴(kuò)展)模塊帖族、Async(異步處理)模塊等栈源。這種“模塊”理念在后續(xù)的Spring 、Spring Boot和Spring Cloud版本中都一直被使用竖般,這種模塊化的注解均以 @Enable作為前綴甚垦,如下所示:

框架實(shí)現(xiàn) @Enable注解模塊 激活模塊
Spring Framework @EnableWebMvc Web Mvc 模塊
/ @EnableTransactionManagement 事物管理模塊
/ @EnableWebFlux Web Flux 模塊
Spring Boot @EnableAutoConfiguration 自動(dòng)裝配模塊
/ @EnableConfigurationProperties 配置屬性綁定模塊
/ @EnableOAuth2Sso OAuth2 單點(diǎn)登陸模塊
Spring Cloud @EnableEurekaServer Eureka 服務(wù)器模塊
/ @EnableFeignClients Feign 客戶端模塊
/ @EnableZuulProxy 服務(wù)網(wǎng)關(guān) Zuul 模塊
/ @EnableCircuitBreaker 服務(wù)熔斷模塊

引入模塊驅(qū)動(dòng)的意義在于簡化裝配步驟,屏蔽了模塊中組件集合裝配的細(xì)節(jié)捻激。但該模式必須手動(dòng)觸發(fā)制轰,也就是將該注解標(biāo)注在某個(gè)配置Bean中,同時(shí)理解原理和加載機(jī)制的成本較高胞谭。那么垃杖,Spring是如何實(shí)現(xiàn) @Enable 模塊呢?主要有以下兩種方式丈屹。

1调俘、Spring框架中@Enable實(shí)現(xiàn)方式

  • 基于 @Import 注解

首先伶棒,參考 @EnableWebMvc 的實(shí)現(xiàn):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
    
}

@Configuration 
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    ... 
}

這種實(shí)現(xiàn)模式主要是通過 @Import 導(dǎo)入配置類 DelegatingWebMvcConfiguration ,而該類標(biāo)注了 @Configuration 注解彩库,表明這是個(gè)配置類肤无,我們都知道@EnableWebMvc 是用來激活Web MVC模塊,所以如HandlerMapping骇钦、HandlerAdapter這些和MVC相關(guān)的組件都是在這個(gè)配置類中被組裝宛渐,這也就是所謂的模塊理念。

  • 基于接口編程

基于接口編程同樣有兩種實(shí)現(xiàn)方式眯搭,第一種參考@EnableCaching的實(shí)現(xiàn):

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class) 
public @interface EnableCaching {
    ...
}

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    
    @Override
    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) { //switch語句選擇實(shí)現(xiàn)模式
            case PROXY:
                return new String[]{AutoProxyRegistrar.class.getName(), ProxyCachingConfiguration.class.getName()};
            case ASPECTJ:
                return new String[]{AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME};
            default:
        }
    }
}

這種方式主要是繼承 ImportSelector接口(AdviceModeImportSelector實(shí)現(xiàn)了ImportSelector接口)窥翩,然后實(shí)現(xiàn) selectImports 方法,通過入?yún)⑦M(jìn)而動(dòng)態(tài)的選擇一個(gè)或多個(gè)類進(jìn)行導(dǎo)入鳞仙,相較于注解驅(qū)動(dòng)寇蚊,此方法更具有彈性。

第二種參考 @EnableApolloConfig 的實(shí)現(xiàn):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ApolloConfigRegistrar.class)
public @interface EnableApolloConfig {
    ....
}
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ....

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
        PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
        PropertySourcesProcessor.class);

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
        ApolloAnnotationProcessor.class);

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
            ApolloJsonValueProcessor.class);
    }
}

這種方式主要是通過 @Import導(dǎo)入實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口的類棍好,在該類中重寫registerBeanDefinitions 方法仗岸,通過 BeanDefinitionRegistry 直接手動(dòng)注冊(cè)和該模塊相關(guān)的組件。接下來借笙,我們用這兩種方式實(shí)現(xiàn)自定義的 @Enable模塊扒怖。

2、自定義@Enable模塊實(shí)現(xiàn)

  • 基于 @Import 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}
@Configuration
public class HelloWorldConfiguration {

    // 可以做一些組件初始化的操作提澎。

    @Bean
    public String helloWorld(){ 
        return "hello world";
    }
    // ....
}
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
                .web(WebApplicationType.NONE).run(args);
        String helloWorld = context.getBean("helloWorld",String.class);
        System.out.println(helloWorld );
    }
}

這里我們自定義了一個(gè) @EnableHelloWorld 注解姚垃,再用 @Import導(dǎo)入一個(gè)自定義的配置類 HelloWorldConfiguration,在這個(gè)配置類中初始化 helloWorld盼忌。

  • 基于接口編程

第一種基于 ImportSelector 接口:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
public class HelloWorldImportSelector implements ImportSelector {
    /**
     * 這種方法比較有彈性:
     *  可以調(diào)用importingClassMetadata里的方法來進(jìn)行條件過濾
     *  具體哪些方法參考:https://blog.csdn.net/f641385712/article/details/88765470
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        if (importingClassMetadata.hasAnnotation("com.loong.case3.spring.annotation.EnableHelloWorld")) {
           return new String[]{HelloWorldConfiguration.class.getName()};
        }
    }
}
@Configuration
public class HelloWorldConfiguration {
    // 可以做一些組件初始化的操作

    @Bean
    public String helloWorld(){ 
        return "hello world";
    }
    // ....
}
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
                .web(WebApplicationType.NONE).run(args);
        String helloWorld = context.getBean("helloWorld",String.class);
        System.out.println(helloWorld );
    }
}

這里我們同樣是自定義 @EnableHelloWorld 注解积糯,通過 @Import 導(dǎo)入 HelloWorldImportSelector 類,該類實(shí)現(xiàn)了 ImportSelector 接口谦纱,在重寫的方法中通過 importingClassMetadata.hasAnnotation("com.loong.case3.spring.annotation.EnableHelloWorld")判斷該類是否標(biāo)注了 @EnableHelloWorld注解看成,從而導(dǎo)入HelloWorldConfiguration 類,進(jìn)行初始化工作跨嘉。

第二種基于 ImportBeanDefinitionRegistrar接口:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldRegistrar.class)
public @interface EnableHelloWorld {
}
public class HelloWorldRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        if (annotationMetadata.hasAnnotation("com.loong..case4.spring.annotation.EnableHelloWorld")) {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(HelloWorldConfiguration.class);
            beanDefinitionRegistry.registerBeanDefinition(HelloWorldConfiguration.class.getName(), beanDefinition);
        }
    }
}
@Configuration
public class HelloWorldConfiguration {
    public HelloWorldConfiguration() {
        System.out.println("HelloWorldConfiguration初始化....");
    }
}
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
                .web(WebApplicationType.NONE).run(args);
    }
}

這里就是在 HelloWorldRegistrar中利用 BeanDefinitionRegistry直接注冊(cè) HelloWorldConfiguration川慌。

(四)Spring 條件裝配

????????條件裝配指的是通過一些列操作判斷是否裝配 Bean ,也就是 Bean 裝配的前置判斷祠乃。實(shí)現(xiàn)方式主要有兩種:@Profile@Conditional梦重,這里我們主要講 @Conditional 的實(shí)現(xiàn)方式,因?yàn)?@Profile 在 Spring 4.0 后也是通過 @Conditional來實(shí)現(xiàn)亮瓷。

@Conditional(HelloWorldCondition.class)
@Component
public class HelloWorldConfiguration {
    public HelloWorldConditionConfiguration (){
        System.out.println("HelloWorldConfiguration初始化琴拧。。嘱支。");
    }
}
public class HelloWorldCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // ...
        return true;
    }
}

這里通過自定義一個(gè) HelloWorldConfiguration 配置類蚓胸,再標(biāo)注 @Conditional 注解導(dǎo)入 HelloWorldCondition類挣饥,該類必須實(shí)現(xiàn) Condition 接口,然后重寫matches方法沛膳,在方法中可以通過兩個(gè)入?yún)慝@取一系列的上下文數(shù)據(jù)和元數(shù)據(jù)扔枫,最終返回ture或false來判定該類是否初始化,

(五)總結(jié)

????????關(guān)于Spring注解驅(qū)動(dòng)的概念就告一段落锹安,最后來簡單的回顧下這篇文章的內(nèi)容短荐,這篇文章主要講了 Spring 注解相關(guān)的幾個(gè)概念:Spring模式注解、@Enable 模塊驅(qū)動(dòng)和 Spring 的條件裝配八毯。其中 Spring 模式注解的核心是 @Component搓侄,所有的模式注解均被它標(biāo)注,而對(duì)應(yīng)兩種裝配方式其實(shí)是尋找 @Component的過程话速。Spring @Enable 模塊的核心是在 @Enable 注解上通過 @Import導(dǎo)入配置類 ,從而在該配置類中實(shí)現(xiàn)和當(dāng)前模塊相關(guān)的組件初始化工作芯侥〔唇唬可以看到,Spring 組件裝配并不具備自動(dòng)化柱查,都需要手動(dòng)的標(biāo)注各種注解廓俭,且之間需相互配合,所以下一章我們就來講講 Spring Boot是如何基于 Spring 注解驅(qū)動(dòng)來實(shí)現(xiàn)自動(dòng)裝配的唉工。

以上就是本章的內(nèi)容研乒,如過文章中有錯(cuò)誤或者需要補(bǔ)充的請(qǐng)及時(shí)提出,本人感激不盡淋硝。



參考:

《Spring Boot 編程思想》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雹熬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谣膳,更是在濱河造成了極大的恐慌竿报,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件继谚,死亡現(xiàn)場(chǎng)離奇詭異烈菌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)花履,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門芽世,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诡壁,你說我怎么就攤上這事济瓢。” “怎么了欢峰?”我有些...
    開封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵葬荷,是天一觀的道長涨共。 經(jīng)常有香客問我,道長宠漩,這世上最難降的妖魔是什么举反? 我笑而不...
    開封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮扒吁,結(jié)果婚禮上火鼻,老公的妹妹穿的比我還像新娘。我一直安慰自己雕崩,他們只是感情好魁索,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盼铁,像睡著了一般粗蔚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饶火,一...
    開封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天鹏控,我揣著相機(jī)與錄音,去河邊找鬼肤寝。 笑死当辐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲤看。 我是一名探鬼主播缘揪,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼义桂!你這毒婦竟也來了找筝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤澡刹,失蹤者是張志新(化名)和其女友劉穎呻征,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罢浇,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陆赋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嚷闭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攒岛。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胞锰,靈堂內(nèi)的尸體忽然破棺而出灾锯,到底是詐尸還是另有隱情,我是刑警寧澤嗅榕,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布顺饮,位于F島的核電站吵聪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏兼雄。R本人自食惡果不足惜吟逝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赦肋。 院中可真熱鬧块攒,春花似錦、人聲如沸佃乘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趣避。三九已至庞呕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間程帕,已是汗流浹背千扶。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骆捧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓髓绽,卻偏偏與公主長得像敛苇,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顺呕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

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