這些不知道,別說你熟悉 Spring

大家好蒙挑,這篇文章跟大家來聊下 Spring 中提供的常用擴展點宗侦、Spring SPI 機制、以及 SpringBoot 自動裝配原理忆蚀,重點介紹下 Spring 基于這些擴展點怎么跟配置中心(Apollo矾利、Nacos懊悯、Zookeeper、Consul)等做集成梦皮。

寫在前面

我們大多數(shù) Java 程序員的日常工作基本都是在做業(yè)務(wù)開發(fā)炭分,俗稱 crudboy。

作為 crudboy 的你有沒有這些煩惱呢剑肯?

  1. 隨著業(yè)務(wù)的迭代捧毛,新功能的加入,代碼變得越來越臃腫让网,可維護性越來越低呀忧,慢慢變成了屎山

  2. 遇到一些框架層的問題不知道怎么解決

  3. 面試被問到使用的框架、中間件原理溃睹、源碼層?xùn)|西而账,不知道怎么回答

  4. 寫了 5 年代碼了,感覺自己的技術(shù)沒有理想的長進

如果你有上述這些煩惱因篇,我想看優(yōu)秀框架的源碼會是一個很好的提升方式泞辐。通過看源碼,我們能學(xué)到業(yè)界大佬們優(yōu)秀的設(shè)計理念竞滓、編碼風(fēng)格咐吼、設(shè)計模式的使用、高效數(shù)據(jù)結(jié)構(gòu)算法的使用商佑、魔鬼細(xì)節(jié)的巧妙應(yīng)用等等锯茄。這些東西都是助力我們成為一個優(yōu)秀工程師不可或缺的。

如果你打算要看源碼了茶没,優(yōu)先推薦 Spring肌幽、Netty、Mybatis抓半、JUC 包喂急。

Spring 擴展

我們知道 Spring 提供了很多的擴展點,第三方框架整合 Spring 其實大多也都是基于這些擴展點來做的琅关。所以熟練的掌握 Spring 擴展能讓我們在閱讀源碼的時候能快速的找到入口煮岁,然后斷點調(diào)試,一步步深入框架內(nèi)核涣易。

這些擴展包括但不限于以下接口:

BeanFactoryPostProcessor:在 Bean 實例化之前對 BeanDefinition 進行修改

BeanPostProcessor:在 Bean 初始化前后對 Bean 進行一些修改包裝增強,比如返回代理對象

Aware:一個標(biāo)記接口冶伞,實現(xiàn)該接口及子接口的類會收到 Spring 的通知回調(diào)新症,賦予某種 Spring 框架的能力,比如 ApplicationContextAware响禽、EnvironmentAware 等

ApplicationContextInitializer:在上下文準(zhǔn)備階段徒爹,容器刷新之前做一些初始化工作荚醒,比如我們常用的配置中心 client 基本都是繼承該初始化器,在容器刷新前將配置從遠(yuǎn)程拉到本地隆嗅,然后封裝成 PropertySource 放到 Environment 中供使用

ApplicationListener:Spring 事件機制界阁,監(jiān)聽特定的應(yīng)用事件(ApplicationEvent),觀察者模式的一種實現(xiàn)

FactoryBean:用來自定義 Bean 的創(chuàng)建邏輯(Mybatis胖喳、Feign 等等)

ImportBeanDefinitionRegistrar:定義@EnableXXX 注解泡躯,在注解上 Import 了一個 ImportBeanDefinitionRegistrar,實現(xiàn)注冊 BeanDefinition 到容器中

InitializingBean:在 Bean 初始化時會調(diào)用執(zhí)行一些初始化邏輯

ApplicationRunner/CommandLineRunner:容器啟動后回調(diào)丽焊,執(zhí)行一些初始化工作

上述列出了幾個比較常用的接口较剃,但是 Spring 擴展遠(yuǎn)不于此,還有很多擴展接口大家可以自己去了解技健。

Spring SPI 機制

在講接下來內(nèi)容之前写穴,我們先說下 Spring 中的 SPI 機制。Spring 中的 SPI 主要是利用 META-INF/spring.factories 文件來實現(xiàn)的雌贱,文件內(nèi)容由多個 k = list(v) 的格式組成啊送,比如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.dtp.starter.adapter.dubbo.autoconfigure.ApacheDubboTpAutoConfiguration,\
  com.dtp.starter.adapter.dubbo.autoconfigure.AlibabaDubboTpAutoConfiguration

org.springframework.boot.env.EnvironmentPostProcessor=\
  com.dtp.starter.zookeeper.autoconfigure.ZkConfigEnvironmentProcessor

這些 spring.factories 文件可能是位于多個 jar 包中,Spring 容器啟動時會通過 ClassLoader.getResources() 獲取這些 spring.factories 文件的全路徑欣孤。然后遍歷路徑以字節(jié)流的形式讀取所有的 k = list(v) 封裝到到一個 Map 中删掀,key 為接口全限定類名,value 為所有實現(xiàn)類的全限定類名列表导街。

上述說的這些加載操作都封裝在 SpringFactoriesLoader 類里披泪。該類很簡單,提供三個加載方法搬瑰、一個實例化方法款票,還有一個 cache 屬性,首次加載到的數(shù)據(jù)會保存在 cache 里泽论,供后續(xù)使用艾少。

[圖片上傳失敗...(image-b8885-1665312357294)]

SpringBoot 核心要點

上面講的 SPI 其實就是我們 SpringBoot 自動裝配的核心。

何為自動裝配翼悴?

自動裝配對應(yīng)的就是手動裝配缚够,在沒 SpringBoot 之前,我們使用 Spring 就是用的手動裝配模式鹦赎。在使用某項第三方功能時谍椅,我們需要引入該功能依賴的所有包,并測試保證這些引入包版本兼容古话。然后在 XML 文件里進行大量標(biāo)簽配置雏吭,非常繁瑣。后來 Spring4 里引入了 JavaConfig 功能陪踩,利用 @Configuration + @Bean 來代替 XML 配置杖们,雖然對開發(fā)來說是友好了許多悉抵,但是這些模板式配置代碼還是很繁瑣,會浪費大量時間做配置摘完。Java 重可能也就是這個時候給人留的一種印象姥饰。

在該背景下出現(xiàn)了 SpringBoot,SpringBoot 可以說是穩(wěn)住了 Java 的地位孝治。SpringBoot 提供了自動裝配功能列粪,自動裝配簡單來說就是將某種功能(如 web 相關(guān)、redis 相關(guān)荆秦、logging 相關(guān)等)打包在一起篱竭,統(tǒng)一管理依賴包版本,并且約定好相關(guān)功能 Bean 的裝配規(guī)則步绸,使用者只需引入一個依賴掺逼,通過少量注解或簡單配置就可以使用第三方組件提供的功能了。

在 SpringBoot 中這類功能組件有一個好聽的名字叫做 starter瓤介。比如 spring-boot-starter-web吕喘、spring-boot-starter-data-redis、spring-boot-starter-logging 等刑桑。starter 里會通過 @Configuration + @Bean + @ConditionalOnXXX 等注解定義要注入 Spring 中的 Bean氯质,然后在 spring.factories 文件中配置為 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的實現(xiàn),就可以完成自動裝配了祠斧。

具體裝配流程怎么樣的呢闻察?

其實也很簡單,基本都是 Spring 中的知識琢锋,沒啥新穎的辕漂。主要依托于@EnableAutoConfiguration 注解,該注解上會 Import 一個 AutoConfigurationImportSelector吴超,看下繼承關(guān)系钉嘹,該類繼承于 DeferredImportSelector。

[圖片上傳失敗...(image-4393cb-1665312357294)]

主要方法為 getAutoConfigurationEntry()

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
      // 1
      if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
      }
      AnnotationAttributes attributes = getAttributes(annotationMetadata);
      // 2
      List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
      configurations = removeDuplicates(configurations);
      // 3
      Set<String> exclusions = getExclusions(annotationMetadata, attributes);
      checkExcludedClasses(configurations, exclusions);
      configurations.removeAll(exclusions);
      // 4
      configurations = getConfigurationClassFilter().filter(configurations);
      fireAutoConfigurationImportEvents(configurations, exclusions);
      return new AutoConfigurationEntry(configurations, exclusions);
    }

方法解讀

  1. 通過 spring.boot.enableautoconfiguration 配置項判斷是否啟用自動裝配鲸阻,默認(rèn)為 true

  2. 使用上述說的 SpringFactoriesLoader.loadFactoryNames() 加載所有 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的實現(xiàn)類的全限定類名跋涣,借助 HashSet 進行去重

  3. 獲取 @EnableAutoConfiguration 注解上配置的要 exclude 的類沦补,然后排除這些特定類

  4. 通過 @ConditionalOnXXX 進行過濾汇荐,滿足條件的類才會留下,封裝到 AutoConfigurationEntry 里返回

那 getAutoConfigurationEntry() 方法在哪兒調(diào)用呢惯吕?

public void refresh() throws BeansException, IllegalStateException {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
    }

以上是 Spring 容器刷新時的幾個關(guān)鍵步驟遣臼,在步驟二 invokeBeanFactoryPostProcessors() 中會調(diào)用所有已經(jīng)注冊的 BeanFactoryPostProcessor 進行處理性置。此處調(diào)用也是有順序的,優(yōu)先會調(diào)用所有 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()揍堰,BeanDefinitionRegistryPostProcessor 是一個特殊的 BeanFactoryPostProcessor鹏浅,然后再調(diào)用所有 BeanFactoryPostProcessor#postProcessBeanFactory()。

ConfigurationClassPostProcessor 是 BeanDefinitionRegistryPostProcessor 的一個實現(xiàn)類屏歹,該類主要用來處理 @Configuration 注解標(biāo)注的類隐砸。我們用 @Configuration 標(biāo)注的類會被 ConfigurationClassParser 解析包裝成 ConfigurationClass 對象,然后再調(diào)用 ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass() 進行 BeanDefination 的注冊蝙眶。

其中 ConfigurationClassParser 解析時會遞歸處理源配置類上的注解(@PropertySource季希、@ComponentScan、@Import幽纷、@ImportResource)式塌、 @Bean 標(biāo)注的方法、接口上的 default 方法友浸,進行 ConfigurationClass 類的補全填充峰尝,同時如果該配置類有父類,同樣會遞歸進行處理收恢。具體代碼請看 ConfigurationClassParser#doProcessConfigurationClass() 方法

protected final SourceClass doProcessConfigurationClass(
            ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
            throws IOException {
      
        // Process any @PropertySource annotations

        // Process any @ComponentScan annotations

        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

        // Process any @ImportResource annotations

        // Process individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
             configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        // Process default methods on interfaces
        processInterfaces(configClass, sourceClass);

        // Process superclass, if any
        if (sourceClass.getMetadata().hasSuperClass()) {
             String superclass = sourceClass.getMetadata().getSuperClassName();
             if (superclass != null && !superclass.startsWith("java") &&
                    !this.knownSuperclasses.containsKey(superclass)) {
                  this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse
                  return sourceClass.getSuperClass();
            }
        }

        // No superclass -> processing is complete
        return null;
    }

1)parser.parse(candidates) 解析得到完整的 ConfigurationClass 對象武学,主要填充下圖框中的四部分。

[圖片上傳失敗...(image-76254-1665312357294)]

[圖片上傳失敗...(image-5c0080-1665312357294)]

2)this.reader.loadBeanDefinitions(configClasses) 根據(jù)框中的四部分進行 BeanDefination 的注冊伦意。

[圖片上傳失敗...(image-5780a8-1665312357294)]

在上述 processImports() 過程中會將 DeferredImportSelector 的實現(xiàn)類放在 deferredImportSelectorHandler 中以便延遲到所有的解析工作完成后進行處理火窒。deferredImportSelectorHandler 中就存放了 AutoConfigurationImportSelector 類的實例。process() 方法里經(jīng)過幾步走會調(diào)用到 AutoConfigurationImportSelector#getAutoConfigurationEntry() 方法上獲取到自動裝配需要的類驮肉,然后進行與上述同樣的 ConfigurationClass 解析封裝工作熏矿。

[圖片上傳失敗...(image-aeca6b-1665312357294)]

[圖片上傳失敗...(image-c76052-1665312357294)]

代碼層次太深,調(diào)用太復(fù)雜离钝,建議自己斷點調(diào)試源碼跟一遍印象會更深刻票编。

ApplicationContextInitializer 調(diào)用時機

我們就以 SpringBoot 項目為例來看,在 SpringApplication 的構(gòu)造函數(shù)中會進行 ApplicationContextInitializer 的初始化奈辰。

[圖片上傳失敗...(image-6fc3d8-1665312357294)]

上圖中的 getSpringFactoriesInstances 方法內(nèi)部其實就是調(diào)用 SpringFactoriesLoader.loadFactoryNames 獲取所有 ApplicationContextInitializer 接口的實現(xiàn)類栏妖,然后反射創(chuàng)建對象,并對這些對象進行排序(實現(xiàn)了 Ordered 接口或者加了 @Order 注解)奖恰。

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
      ClassLoader classLoader = getClassLoader();
      // Use names and ensure unique to protect against duplicates
      Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
      List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
      AnnotationAwareOrderComparator.sort(instances);
      return instances;
    }

至此吊趾,項目中所有 ApplicationContextInitializer 的實現(xiàn)已經(jīng)加載并且創(chuàng)建好了。在 prepareContext 階段會進行所有已注冊的 ApplicationContextInitializer#initialize() 方法的調(diào)用瑟啃。在此之前prepareEnvironment 階段已經(jīng)準(zhǔn)備好了環(huán)境信息论泛,此處接入配置中心就可以拉到遠(yuǎn)程配置信息然后填充到 Spring 環(huán)境中供應(yīng)用使用。

[圖片上傳失敗...(image-598313-1665312357294)]

SpringBoot 集成 Apollo

ApolloApplicationContextInitializer 實現(xiàn) ApplicationContextInitializer 接口蛹屿,并且在 spring.factories 文件中配置如下

[圖片上傳失敗...(image-74709c-1665312357294)]

org.springframework.context.ApplicationContextInitializer=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer

initialize() 方法中會根據(jù) apollo.bootstrap.namespaces 配置的 namespaces 進行配置的拉去屁奏,拉去到的配置會封裝成 ConfigPropertySource 添加到 Spring 環(huán)境 ConfigurableEnvironment 中。具體的拉去流程就不展開講了错负,感興趣的可以自己去閱讀源碼了解坟瓢。

SpringCloud 集成 Nacos勇边、Zk、Consul

在 SpringCloud 場景下折联,SpringCloud 規(guī)范中提供了 PropertySourceBootstrapConfiguration 繼承 ApplicationContextInitializer粒褒,另外還提供了個 PropertySourceLocator,二者配合完成配置中心的接入诚镰。

[圖片上傳失敗...(image-b8e0eb-1665312357294)]

initialize 方法根據(jù)注入的 PropertySourceLocator 進行配置的定位獲取奕坟,獲取到的配置封裝成 PropertySource 對象,然后添加到 Spring 環(huán)境 Environment 中清笨。

[圖片上傳失敗...(image-eecbc0-1665312357294)]

Nacos月杉、Zookeeper、Consul 都有提供相應(yīng) PropertySourceLocator 的實現(xiàn)

[圖片上傳失敗...(image-58ed26-1665312357294)]

我們來分析下 Nacos 提供的 NacosPropertySourceLocator抠艾,locate 方法只提取了主要流程代碼苛萎,可以看到 Nacos 啟動會加載以下三種配置文件,也就是我們在 bootstrap.yml 文件里配置的擴展配置 extension-configs跌帐、共享配置 shared-configs 以及應(yīng)用自己的配置首懈,加載到配置文件后會封裝成 NacosPropertySource 放到 Spring 的 Environment 中。

[圖片上傳失敗...(image-a540f3-1665312357294)]

public PropertySource<?> locate(Environment env) {
         loadSharedConfiguration(composite);
         loadExtConfiguration(composite);
         loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
         return composite;
    }

loadApplicationConfiguration 加載應(yīng)用配置時谨敛,同時會加載以下三種配置究履,分別是

  1. 不帶擴展名后綴,application

  2. 帶擴展名后綴脸狸,application.yml

  3. 帶環(huán)境最仑,帶擴展名后綴,application-prod.yml

并且從上到下炊甲,優(yōu)先級依次增高

private void loadApplicationConfiguration(
            CompositePropertySource compositePropertySource, String dataIdPrefix,
            NacosConfigProperties properties, Environment environment) {
        String fileExtension = properties.getFileExtension();
        String nacosGroup = properties.getGroup();
        // load directly once by default
        loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
                fileExtension, true);
        // load with suffix, which have a higher priority than the default
        loadNacosDataIfPresent(compositePropertySource,
                dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
        // Loaded with profile, which have a higher priority than the suffix
        for (String profile : environment.getActiveProfiles()) {
            String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
            loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
                    fileExtension, true);
        }
    }

加載過程中泥彤,通過 namespace, dataId, group 唯一定位一個配置文件

  1. 首先獲取本地緩存的配置,如果有直接返回

  2. 如果步驟1從本地沒找到相應(yīng)配置文件卿啡,開始從遠(yuǎn)處拉去吟吝,Nacos 2.0 以上版本使用 Grpc 協(xié)議進行遠(yuǎn)程通信,1.0 及以下使用 Http 協(xié)議進行遠(yuǎn)程通信

  3. 對拉去到的字符串進行解析颈娜,封裝成 NacosPropertySource 返回

具體細(xì)節(jié)就不展開講了剑逃,可以自己看源碼了解

Zookeeper、Consul 的接入也是非常簡單官辽,可以自己分析一遍蛹磺。如果我們有自研的配置中心,需要在 SpringCloud 環(huán)境下使用同仆,可以根據(jù) SpringCloud 提供的這些擴展參考以上幾種實現(xiàn)快速的寫個 starter 進行接入萤捆。

總結(jié)

本篇文章主要講了下 Spring SPI 機制、SpringBoot 自動裝配原理,以及擴展點 ApplicationContextInitializer 在集成配置中心時的應(yīng)用俗或。篇幅有限市怎,一些具體代碼細(xì)節(jié)就沒展開講了,以后會出些文章針對某一個點進行詳細(xì)講解蕴侣。

個人開源項目

DynamicTp 是一個基于配置中心實現(xiàn)的輕量級動態(tài)線程池管理工具焰轻,主要功能可以總結(jié)為動態(tài)調(diào)參臭觉、通知報警昆雀、運行監(jiān)控、三方包線程池管理等幾大類蝠筑。

[圖片上傳失敗...(image-d86dcc-1665312357294)]

目前累計 2k star狞膘,代碼優(yōu)雅,使用了大量設(shè)計模式什乙,如果你覺得看這些大型框架源碼費勁挽封,那么可以嘗試從 DynamicTp 源碼入手,歡迎大家了解試用

官網(wǎng)https://dynamictp.cn

gitee地址https://gitee.com/dromara/dynamic-tp

github地址https://github.com/dromara/dynamic-tp

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末臣镣,一起剝皮案震驚了整個濱河市辅愿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忆某,老刑警劉巖点待,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異弃舒,居然都是意外死亡癞埠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門聋呢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苗踪,“玉大人,你說我怎么就攤上這事削锰⊥ú” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵器贩,是天一觀的道長颅夺。 經(jīng)常有香客問我,道長磨澡,這世上最難降的妖魔是什么碗啄? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮稳摄,結(jié)果婚禮上稚字,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好胆描,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布瘫想。 她就那樣靜靜地躺著,像睡著了一般昌讲。 火紅的嫁衣襯著肌膚如雪国夜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天短绸,我揣著相機與錄音车吹,去河邊找鬼。 笑死醋闭,一個胖子當(dāng)著我的面吹牛窄驹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播证逻,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼乐埠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了囚企?” 一聲冷哼從身側(cè)響起丈咐,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎龙宏,沒想到半個月后棵逊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡烦衣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年歹河,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片花吟。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秸歧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衅澈,到底是詐尸還是另有隱情键菱,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布今布,位于F島的核電站经备,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏部默。R本人自食惡果不足惜侵蒙,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望傅蹂。 院中可真熱鬧纷闺,春花似錦算凿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至浸卦,卻和暖如春署鸡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背限嫌。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工靴庆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人萤皂。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓撒穷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親裆熙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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