从0-1了解Spring是如何运行起来的(四):BeanFactory后处理初分析,了解如何加载BeanDefinition

前言

最深刻了解一个框架的思想的方式,莫过于看源码,本系列旨在于从Springboot底层源码(Version - 2.6.6)出发,一步步了解springboot是如何运行起来的。

从0-1了解SpringBoot如何运行(一):Environment环境装配

从0-1了解SpringBoot是如何运行起来的(二):定制你的banner

从0-1了解Spring是如何运行起来的(三):Context预处理,为加载容器做准备

在前述的文章中,我们主要了解了SpringBoot是如何实现配置文件的加载、context的创建、banner图打印的流程。这次的话我们主要针对context中的refresh处理进行学习和解析。

public ConfigurableApplicationContext run(String... args) {
    ......
    refreshContext(context);
    ......
}

简介

本章节我们主要针对BeanDefinition的加载以及BeanFactory后处理进行学习。在展开讲解源码之前,我们需要先来了解一下这些概念。

BeanDefinition

BeanDefinition是一个描述 Bean的类实例,其其实是一个描述bean和修改bean信息的一个接口对象。如下是BeanDefinition的一个继承类图:

BeanDefinition

从类图中可以看到,其继承了BeanMetaDataElementAttributeAccessor。前者是Bean的元数据的存储对象,其source属性保存的是Bean实例的最基础的定义信息。而AttributeAccessor则是提供了对BeanDefinition的一些操作接口,包括获取特定的属性、移除特定属性以及添加特定属性的方法。

BeanDefinition在Spring中其实是一个相当重要的概念。其就如同Bean对象的图纸,需要生产什么样子的Bean都由BeanDefinition来定义。 同时结合类图来看,我们不难看到,BeanDefinition还有以下的一些能力:

  • 标记当前的Bean的作用范围;
  • 定义当前Bean的工厂Bean的名称;
  • 当前Bean的初始化方法名称;
  • 当前Bean是否支持懒加载
  • ... ...

通过对BeanDefinition进行加载并存储到BeanDefinitionMap中,后续Spring才能依照这些对应的“图纸”生成出匹配的Bean实例对象。

BeanFactoryPostProcessor

BeanFactoryPostProcessor实际是用来处理BeanFactory的后置处理器。其生效的主要时间段是在Bean实例化之前。另外,BeanFactoryPostProcessor其实也是Spirng提供的一个钩子,其可以让你根据自己的实际情况修改Bean的定义属性。 最常见的应用就是我们的Bean中会有一些占位符,那么在Bean实例化之前这些占位符肯定是要被实际的配置参数填充的,这个填充的过程就是通过BeanFactoryPostProcessor的后置处理完成的。

源码解析:

在了解了上述的基本内容后,我们就可以从源码角度对整个bean的加载过程进行了解和分析了。

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
      // 对刷新做了一些初始化的设置 
       prepareRefresh();
      // 更新对工厂的引用 并 获取容器工厂
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      // 预先对BeanFactory进行相应的处理,如设置上下文的类加载器、默认的Bean等
      prepareBeanFactory(beanFactory);
      try {
         // 抽象类预留的hook方法,允许子类在标准的BeanFactory初始化后,再进行初始化。(模版设计模式)
         postProcessBeanFactory(beanFactory);
         StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
         //关键代码:会在这里调用BeanFactory的后处理器来初始化BeanDefinition,BD会在这里被加载到BDMap中。
         invokeBeanFactoryPostProcessors(beanFactory);
        ......
      }catch (BeansException ex) {
        ...
      }finally {
        ...
      }
   }
}

Refresh(代码的整体结构采用模版设计模式实现,具体的实现由子类实现。由于Refresh部分的整体的代码块相对较长,且包含的内容较多。因此,本期我们只针对invokeBeanFactoryPostProcessors()及其之前的代码进行查看。可以看到,在执行invokeBeanFactoryPostProcessors()之前,Spring对相应的工厂做了一些处理,主要包括

1、发送context刷新的事件

2、更新工厂的引用数据

3、预处理工厂数据,如设置累加载器,注入一些默认的BeanDefinition等。

4、如果这里我们实现了预留的postProcessBeanFactory方法,那么还会执行这个模版钩子。

5、再次发送Bean初始化的事件

6、调用BeanFactory的后处理器。

这里我们跳过前面的几个步骤,直接追入最关键的源代码进行查看:

public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory,              List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
   Set<String> processedBeans = new HashSet<>();
    // 判断当前是否为BeanDef注册器
   if (beanFactory instanceof BeanDefinitionRegistry) {
      BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
      List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
      List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
      for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
         if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
            BeanDefinitionRegistryPostProcessor registryProcessor =
                  (BeanDefinitionRegistryPostProcessor) postProcessor;
            registryProcessor.postProcessBeanDefinitionRegistry(registry);
            registryProcessors.add(registryProcessor);
         }else {
            regularPostProcessors.add(postProcessor);
         }
      }
       //加载第一批次的BeanFactoryPostProcessor,并进行调用(该批次的均实现了PriOrityOrdered.class类)
      List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
      String[] postProcessorNames =
            beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {
         if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
         }
      }
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      registryProcessors.addAll(currentRegistryProcessors);
       //此处调用第一批次的BDF注册处理器。(如configClassPostProcessor,作用是加载所有的ConfigClass的BeanDef)
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
      currentRegistryProcessors.clear();

       //调用第二批次的BDF处理器(此处代码中什么都没做。。。因为没有实现ordered的类 O。O)
      postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {
         if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
         }
      }
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      registryProcessors.addAll(currentRegistryProcessors);
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
      currentRegistryProcessors.clear();
        //循环对第三批次的BDF处理器进行调用,直到无处理器,这里主要是对尚未初始化的BD进行占位符等信息的替换填充,
        //eg. MapperScannerConfigurer会对mapper的基础包路径、作用域范围进行占位符的填充。
      boolean reiterate = true;
      while (reiterate) {
         reiterate = false;
         postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
         for (String ppName : postProcessorNames) {
            if (!processedBeans.contains(ppName)) {
               currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
               processedBeans.add(ppName);
               reiterate = true;
            }
         }
         sortPostProcessors(currentRegistryProcessors, beanFactory);
         registryProcessors.addAll(currentRegistryProcessors);
         invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
         currentRegistryProcessors.clear();
      }
      invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
      invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
   }else {
      invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
   }
        String[] postProcessorNames =
                beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
        //按照实现了PriorityOrder、Ordered和其余的分类方式将处理器分开
        List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
        List<String> orderedPostProcessorNames = new ArrayList<>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<>();
        for (String ppName : postProcessorNames) {
            if (processedBeans.contains(ppName)) {
                // 如果已经处理,则跳过
            }else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
            }else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            }else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }

    // 首先,再次调用实现了priorityOrdered的Processor。
    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
    
   // 接下来再调用一次实现了ordered的Processor。
   List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
   for (String postProcessorName : orderedPostProcessorNames) {
      orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
   }
   sortPostProcessors(orderedPostProcessors, beanFactory);
   invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
   // 最后调用所有其他的后处理器
   List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
   for (String postProcessorName : nonOrderedPostProcessorNames) {
      nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
   }
   invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
   // 清除掉所有的缓存数据信息
   beanFactory.clearMetadataCache();
}

这部分的源码其实较长,整块代码主要是针对于BeanFactoryPostProcessor的执行进行操作的。这里梳理了一下整块代码的执行逻辑:

第一阶段:加载完BD前,调用初始化前的BeanFactoryPostProcessor:

  1. 调用实现了PriorityOrder的BeanFactoryPostProcessor类。(如ConfigurationClassPostProcessor,负责加载beanFactory内的BeanDef)
  2. 调用实现了Ordered的BeanFactoryPostProcessor类。
  3. 执行剩余的所有BeanFactoryPostProcessor类。(如MapperScannerConfigure,对mapper的基本包路径、是否懒加载等进行占位符替换。)

第二阶段: 加载完BD后,调用非提前初始化的 BeanFactoryPostProcessor:

  1. 调用实现了PriorityOrder的BeanFactoryPostProcessor类。(如PropertySourcesPlaceholderConfigurer,对所有的BeanDef的占位符进行填充)

  2. 调用实现了Ordered的BeanFactoryPostProcessor类。(如DependsOnDatabaseInitializationPostProcessor,控制DB的依赖属性)

  3. 执行剩余的所有BeanFactoryPostProcessor类。

首先,根据整个加载流程,可以比较直观的一点是,对于BeanFactory的处理,我们在某种程度上是可以控制其PostProcessor加载调用的先后顺序,这个可以通过PriorityOrder、Ordered等注解进行相应的控制。

接着,我们主要对上述的BeanFactoryPostProcessor中比较重要的两个处理器进行展开分析和学习:

ConfigurationClassPostProcessor

`org.springframework.context.annotation.ConfigurationClassPostProcessor`主要实现的功能是将各处的配置类及其对应的Bean的BeanDefinition扫描到对应的Factory 中。其执行的地方在第一阶段,加载的主要源代码如下所示:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
   List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
   String[] candidateNames = registry.getBeanDefinitionNames();
   //获取此时的候选BeanDef的名字
   for (String beanName : candidateNames) {
      BeanDefinition beanDef = registry.getBeanDefinition(beanName);
      if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
         if (logger.isDebugEnabled()) {
            logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
         }
      } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
         configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
      }
   }
   // 如果当前没有需要加载的配置类,则直接返回
   if (configCandidates.isEmpty()) {
      return;
   }
   // 根据对应的@Order注解的值排序对应的配置候选类
   configCandidates.sort((bd1, bd2) -> {
      int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
      int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
      return Integer.compare(i1, i2);
   });
   // 通过应用上下文来检测所有的自定义bean名称的生成策略。
   SingletonBeanRegistry sbr = null;
   if (registry instanceof SingletonBeanRegistry) {
      sbr = (SingletonBeanRegistry) registry;
      if (!this.localBeanNameGeneratorSet) {
         BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
               AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
         if (generator != null) {
            this.componentScanBeanNameGenerator = generator;
            this.importBeanNameGenerator = generator;
         }
      }
   }
    //如果环境配置为null,则生成一个标准的环境
   if (this.environment == null) {
      this.environment = new StandardEnvironment();
   }
   // 生成一个配置类解析器
   ConfigurationClassParser parser = new ConfigurationClassParser(
         this.metadataReaderFactory, this.problemReporter, this.environment,
         this.resourceLoader, this.componentScanBeanNameGenerator, registry);
   Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
   Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
   do {
      StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
      parser.parse(candidates); // 对配置的候选类进行解析,直到所有的配置类被解析完成,退出循环
      parser.validate();
      Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
      configClasses.removeAll(alreadyParsed);
      // 创建配置类BeanDefinition阅读器
      if (this.reader == null) {
         this.reader = new ConfigurationClassBeanDefinitionReader(
               registry, this.sourceExtractor, this.resourceLoader, this.environment,
               this.importBeanNameGenerator, parser.getImportRegistry());
      }
      //加载配置类BD
      this.reader.loadBeanDefinitions(configClasses);
      alreadyParsed.addAll(configClasses);
      processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
      candidates.clear(); // 清空候选配置类
      if (registry.getBeanDefinitionCount() > candidateNames.length) {
         String[] newCandidateNames = registry.getBeanDefinitionNames();
         Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
         Set<String> alreadyParsedClasses = new HashSet<>();
         for (ConfigurationClass configurationClass : alreadyParsed) {
            alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
         }
         for (String candidateName : newCandidateNames) {
            if (!oldCandidateNames.contains(candidateName)) {
               BeanDefinition bd = registry.getBeanDefinition(candidateName);
               if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                     !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                  candidates.add(new BeanDefinitionHolder(bd, candidateName));
               }
            }
         }
         candidateNames = newCandidateNames;
      }
   }
   while (!candidates.isEmpty());
   if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
      sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
   }
   if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
      ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
   }
}

上述源代码的主要工作流程如下所示:

1、因为整个类的主要功能其实是对配置的类进行加载,那么首先肯定要获取到哪些是我们当前需要加载的类,即找到configCandidates(翻译为:候选的配置)

2、因为Spring本身加载配置类,也是需要一个先后顺序的,那么此时需要先根据@Order的方式来进行排序。

3、因为beanDefinition本身的名字都是由BeanNameGenerator生成的,那么这时肯定需要找到哪个才是我们需要调用的Bean的名字的生成器,从而进行后续BeanDef的名字的生成

4、实例化一个 ConfigurationClassParser ,从名字猜测,就是专门解析我们的配置类的,然后执行parser.parse(candidates);对候选配置类进行解析,在一开始的时候,beanDefinition里面除了那些约定的类,其实只有我们自己的启动类了,而我们的启动类也是有@Configuration的,所以一开始其实就是解析我们的启动类。在解析我们的启动类的时候,不难想象,application类底下肯定还是有许多依赖的类的(通常通过@ComponentScan扫描出来),那么自然也是需要对这些类进行加载的。这里我简单的画了一下不同配置类之间的依赖关系:

其实一看就是一颗典型的多叉树,那么对于多叉树的遍历,脑海中不由自主的其实会想到栈或递归。实际上,Spring也是如此做的。这个流程的大致加载逻辑如下图所示:

执行完了`parser.parse(candidates);`,其实只是将Config类的BeanDefinition给加载了,但是其中可能还有其管理的Bean实例可能都还是没有加载进来的的,那么此时就需要执行一次`this.reader.loadBeanDefinitions(configClasses);`对配置类管理的相关Bean进行加载。

在解析启动类之后,将解析出来的所有类再次减去已经解析的类,就会得到下一批需要解析的启动类,依次循环往返,直到解析完所有的类,那么此时加载就算完成了。

5、在加载完了所有的BeanDef后,会将元信息读取工厂的缓存清除了。由此以来,整个配置类及其依赖的BeanDef的加载流程就宣告结束。

PropertySourcesPlaceholderConfigurer

对于beanDef内占位符的替换,主要由*PropertySourcesPlaceholderConfigurer*实现。其主要的处理步骤如下所示:

1、依据当前工厂的不同environment创建不同的propertyResolver。

2、对不同的propertyResolver设置占位符的前缀(prefix)、后缀(suffix)、以及变量的切分符(valueSeparator)。一般默认会是"${"、"}"和":"

3、根据propertyResolver再创建出BeanDefinitionVisitor,用于对不同的bean进行遍历及变量替换。

替换变量逻辑中的关键的核心代码如下所示:

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
      StringValueResolver valueResolver) {
   BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
    //获取到所有已经加载好的Bd
   String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
   for (String curName : beanNames) {
      // 首先判断不是处理“自己”对应的BD才会进行处理
      if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
         BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
         try {
             //关键代码
            visitor.visitBeanDefinition(bd);
         }
         catch (Exception ex) {
            throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
         }
      }
   }
    //在Spring2.5中会对Alias(同名属性)也进行更新
   beanFactoryToProcess.resolveAliases(valueResolver);
   //会将属性值的处理器保存下来
   beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

执行前:

执行后:

总结

相比前几章,本章从内容来说,是Spring中相对关键关键的内容,主要描述的是关于BeanDef的加载、BeanDef内占位符的数据填充等。最后让我们回过头来,再次审视一次整个refresh()代码执行的逻辑流程:

1、发送context刷新的事件

2、更新工厂的引用数据

3、预处理工厂数据,如设置类加载器,注入一些默认的BeanDefinition等。

4、如果这里我们实现了预留的postProcessBeanFactory方法,那么还会执行这个模版钩子。

5、再次发送Bean初始化的事件

6、调用BeanFactory的后处理器。

  • 第一阶段:加载完BD前,调用初始化前的BeanFactoryPostProcessor:

    1. 调用实现了PriorityOrder的BeanFactoryPostProcessor类。(如ConfigurationClassPostProcessor,负责加载beanFactory内的BeanDef)
    2. 调用实现了Ordered的BeanFactoryPostProcessor类。
    3. 执行剩余的所有BeanFactoryPostProcessor类。(如MapperScannerConfigure,对mapper的基本包路径、是否懒加载等进行占位符替换。)
  • 第二阶段: 加载完BD后,调用非提前初始化的 BeanFactoryPostProcessor:

    1. 调用实现了PriorityOrder的BeanFactoryPostProcessor类。(如PropertySourcesPlaceholderConfigurer,对所有的BeanDef的占位符进行填充)

    2. 调用实现了Ordered的BeanFactoryPostProcessor类。(如DependsOnDatabaseInitializationPostProcessor,控制DB的依赖属性)

    3. 执行剩余的所有BeanFactoryPostProcessor类。

归纳成流程图,整个流程就可以描述成如下的形式:

参考文章

Spring IOC 有什么好处呢

Spring(四)核心容器 - BeanDefinition 解析

Spring源码-学习随笔(三)springboot的类加载beanDefinition的过程简述

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容