spring源码解析------@Import注解解析与ImportSelector,ImportBeanDefinitionRegistrar以及DeferredImportSelector区别

1.@Import注解在springBoot中间接的广泛应用

 在springboot中并没有直接显式的使用@Import标签,而是通过@Import标签来间接的提供了很多自动配置的注解。比如@EnableAutoConfiguration@EnableConfigurationProperties等。这些标签的实现都是通过使用@Import标签来完成的。

......
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ......
}
......
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
    ......
}

 可以发现都是通过@Import来完成的。

2.spring中的@Import注解

2.1@Import注解的作用

@Import标签可以导入一个或者多个组件类,通常是@Configuration 注入的bean。提供了与xml中<import/>标签类型的功能,能够导入@Configuration类。

public @interface Import {

    /**
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
     */
    Class<?>[] value();

}

 导入的类可以是@Configuration类型的配置类,实现了ImportSelector接口的类,实现了ImportBeanDefinitionRegistrar的类或者常规的组件类。

2.2@Import的解析前的处理

@Import处理的位置其实跟@Conditional注解都在同一个类中,处理的时机也是一样的。这里可以去看看@Conditional注解解析的逻辑

2.2.1 容器的刷新时候的准备

 在容器刷新方法就定义在AbstractApplicationContext类的refresh方法中。在这个里面会有解析注册以及实例话bean和其他的步骤,我们要看的就是解析跟注册步骤。

    public void refresh() throws BeansException, IllegalStateException {
    ......
    invokeBeanFactoryPostProcessors(beanFactory);
    }

 在invokeBeanFactoryPostProcessors方法中会实例化所有的BeanFactoryPostProcessor类型的类并调用实现的postProcessBeanFactory方法。

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    ......
}
2.2.2 PostProcessorRegistrationDelegate处理BeanDefinitionRegistry以及BeanFactoryPostProcessor

 这里直接进入到invokeBeanFactoryPostProcessors方法。会有两种处理方式:

  1. 当前的beanFactoryBeanDefinitionRegistry类型的
    (1) 先处理beanFactory,然后按照是否实现了PriorityOrdered,Ordered以及两个都没有实现的顺序来处理BeanDefinitionRegistryPostProcessor接口的实现类。
    (2)处理实现了BeanFactoryPostProcessor接口的子类依次调用实现的postProcessBeanFactory方法
  2. 当前的beanFactory不是BeanDefinitionRegistry类型的,则直接处理实现了BeanFactoryPostProcessor接口的子类依次调用实现的postProcessBeanFactory方法

 其中会处理@Import标签的ConfigurationClassPostProcessor实现类BeanDefinitionRegistryPostProcessor接口跟PriorityOrdered接口间接实现了BeanFactoryPostProcessor接口因此无论怎么样都会被调用的。

    public static void invokeBeanFactoryPostProcessors(
            ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
        //如果当前的beanFactory是BeanDefinitionRegistry的,则需要将已经存在的beanDefinition进行注册
        if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
            //保存注册bean的BeanDefinitionRegistryPostProcessor
            List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
            //迭代beanFactoryPostProcessors如果是BeanDefinitionRegistryPostProcessor子类则加入到registryProcessors集合中
            for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
                if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                    BeanDefinitionRegistryPostProcessor registryProcessor =
                            (BeanDefinitionRegistryPostProcessor) postProcessor;
                    //处理beanFactory中的beanDefinitionNames
                    registryProcessor.postProcessBeanDefinitionRegistry(registry);
                    registryProcessors.add(registryProcessor);
                }
                else {
                    regularPostProcessors.add(postProcessor);
                }
            }
            List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
            //先处理同时实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor的实现类
            String[] postProcessorNames =
                    beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                //如果当前的BeanDefinitionRegistryPostProcessor类的实现类也实现了PriorityOrdered类,则加入当当前需要注册的BeanDefinitionRegistryPostProcessor集合
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            //进行排序
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            //加入到registryProcessors集合
            registryProcessors.addAll(currentRegistryProcessors);
            //调用实现了BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();
            ......
            invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
            invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
    }else {
            // Invoke factory processors registered with the context instance.
            invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
    }
}
2.2.3 ConfigurationClassPostProcessorprocessConfigBeanDefinitionsprocessConfigBeanDefinitions解析bean

 上面提到了ConfigurationClassPostProcessor无论怎么样都会被调用。这里先看看实现的postProcessBeanDefinitionRegistry方法跟postProcessBeanFactory的共同点。

    //在refresh方法中最先被调用
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        //给当前的BeanDefinitionRegistry类型的beanFactory设置一个全局hash值,避免重复处理
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        //加入到已经处理的PostProcessed集合中
        this.registriesPostProcessed.add(registryId);
        //进行处理
        processConfigBeanDefinitions(registry);
    }

    /**
     * Prepare the Configuration classes for servicing bean requests at runtime by replacing them with CGLIB-enhanced subclasses.
     */
    //在refresh方法中第二被调用
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        //给当前的ConfigurableListableBeanFactory类型的beanFactory设置一个全局hash值,避免重复处理
        int factoryId = System.identityHashCode(beanFactory);
        if (this.factoriesPostProcessed.contains(factoryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + beanFactory);
        }
        //加入到已经处理的集合中
        this.factoriesPostProcessed.add(factoryId);
        //如果当前的beanFactory不在registriesPostProcessed中则进行处理
        if (!this.registriesPostProcessed.contains(factoryId)) {
            // BeanDefinitionRegistryPostProcessor hook apparently not supported...
            // Simply call processConfigurationClasses lazily at this point then.
            processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
        }
        //将beanFactory中用Configuration注解配置的bean进行动态加强
        enhanceConfigurationClasses(beanFactory);
        //增加一个ImportAwareBeanPostProcessor
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }

 发现一个共同点就是,如果没有解析过的beanFactory进来都会调用processConfigBeanDefinitions方法来处理Configuration类。在这个方法中有两个解析的位置。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//循环处理知道所有的bean都处理完,包含bean内部定义的bean
        do {
            //第一个解析的位置,这里是解析能够直接获取的候选配置bean。可能是Component,ComponentScan,Import,ImportResource或者有Bean注解的bean
            parser.parse(candidates);
            parser.validate();
            //获取上面封装已经解析过的配置bean的ConfigurationClass集合
            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            //移除前面已经处理过的
            configClasses.removeAll(alreadyParsed);

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            //第二个解析的位置,这里是加载configurationClasse中内部可能存在配置bean,比如方法上加了@Bean或者@Configuration标签的bean
            this.reader.loadBeanDefinitions(configClasses);
            ......
            }
            ......
}

 两个解析的位置分别是:

  1. 第一个位置是ConfigurationClassParser类的parse方法,解析能够直接从beanFactory中获取的候选bean。
  2. 第二个是ConfigurationClassBeanDefinitionReaderloadBeanDefinitions方法,解析从直接获取的候选bean中内部定义的bean。
2.2.4 ConfigurationClassParser的解析过程

ConfigurationClassParserparse有很多重载的方法,但是内部逻辑都是调用的processConfigurationClass方法。

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
            //解析bean
                if (bd instanceof AnnotatedBeanDefinition) {
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                    parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                }
                else {
                    parse(bd.getBeanClassName(), holder.getBeanName());
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }
        //处理DeferredImportSelector类型的实现类
        this.deferredImportSelectorHandler.process();
    }

    protected final void parse(@Nullable String className, String beanName) throws IOException {
        ......
        processConfigurationClass(new ConfigurationClass(reader, beanName));
    }

    protected final void parse(Class<?> clazz, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(clazz, beanName));
    }

    protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(metadata, beanName));
    }

 从上面可以看出来,主要要看的逻辑还是在processConfigurationClass方法中。这里还需要注意一点就是在最上面的parse方法最后有一个逻辑this.deferredImportSelectorHandler.process()这个逻辑是处理@Import注解中指定的引入类是DeferredImportSelectorHandler子类的情况的。
 进入到processConfigurationClass方法,关于这个方法在@Conditional注解解析的逻辑这个里面说到过。这里只需要关注内部的doProcessConfigurationClass方法的逻辑。

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        //检查当前解析的配置bean是否包含Conditional注解,如果不包含则不需要跳过
        // 如果包含了则进行match方法得到匹配结果,如果是符合的并且设置的配置解析策略是解析阶段不需要调过
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }
        //从缓存中尝试获取当前配置bean解析之后的ConfigurationClass对象
        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if (existingClass != null) {
            //检查当前这个配置bean是通过@Import标签引入的还是自动注入到另外一个配置类bean里面的
            if (configClass.isImported()) {
                //如果是通过@Import标签引入则将当前解析的配置bean加入到已经存在的解析过的bean的用来保存通过@Import标签引入的bean的集合中
                if (existingClass.isImported()) {
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                //将当前解析的配置bean代替之前的
                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }
        }

        // Recursively process the configuration class and its superclass hierarchy.
        //递归获取原始的配置类信息然后封装为SourceClass
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            //处理configClass
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);
        //保存到configurationClasses中
        this.configurationClasses.put(configClass, configClass);
    }

 在doProcessConfigurationClass方法中有很多标签的处理逻辑,比如@Component@PropertySources@ComponentScans等。对于@Import注解的处理方式,封装在了另外的一个方法processImports里面。

    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {
                .......
                        processImports(configClass, sourceClass, getImports(sourceClass), true);
                ......
            }
2.3 @Import注解的解析
2.3.1 processImports方法

 已经知道@Import注解在那个方法里面进行解析了。先看看对应的方法参数的含义。

参数 含义
ConfigurationClass configClass 当前需要解析的Configuration类
SourceClass currentSourceClass 类的源数据封装对象
Collection<SourceClass> importCandidates Configuration类引入的其他类,以及其他类中引入的别的类,比如A引入B,B引入C,C引入D。那么这个就包含了B,C,D
boolean checkForCircularImports 是否检查循环引入

 现在对方法内部进行分析

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
        //检查包含有Import注解的集合是不是空的,空的则表示没有
        if (importCandidates.isEmpty()) {
            return;
        }
        //检查是否存在循环引入,通过deque的方式来检查
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            //将当前bean放到importStack中,用于检查循环引入
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    //import指定的Bean是ImportSelector类型
                    if (candidate.isAssignable(ImportSelector.class)) {
                        //实例化指定的ImportSelector子类
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                this.environment, this.resourceLoader, this.registry);
                        //如果是ImportSelector的子类DeferredImportSelector的子类则按照DeferredImportSelectorHandler逻辑进行处理
                        if (selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        }
                        else {//如果不是则获取指定的需要引入的class的ClassNames
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            //根据ClassNames获取并封装成一个SourceClass
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            //继续调用processImports处理
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }//如果是ImportBeanDefinitionRegistrar的子类
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
                        //如果对应的ImportBeanDefinitionRegistrar子类对象,并放到configClass,这个是用来注册额外的bean的
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                        this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {//不是上面任何类的子类就可以进行处理了,将指定的需要引入的bean转化为ConfigurationClass,然后到processConfigurationClass方法中处理
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }

 主要的步骤如下:

  1. 检查解析出来的Import注解集合是不是空的,空的则不处理,直接返回
  2. 如果设置了需要检查循环依赖,则进行循环依赖的检查,不需要则跳过,进入3步骤
  3. 进行处理
    (1) 将当前bean放到importStack中,用于检查循环引入
    (2)循环处理以下情况
      1)如果是ImportSelector类型的子类。先实例化这个类,然后检查这个类是不是DeferredImportSelector子类,是的则调用ConfigurationClassParser的内部类DeferredImportSelectorHandlerhandle方法处理。如果不是DeferredImportSelector子类则继续调用processImports方法处理。按照步骤1从头开始。
      2)如果是ImportBeanDefinitionRegistrar类型的子类,则实例化这个类,然后放到当前解析的bean的importBeanDefinitionRegistrars属性中,后面注册bean时候调用。
      3)没有实现以上任何接口,则将当前的被引入的bean加入到这个bean的importedBy属性中,然后调用processConfigurationClass方法。

 对上面的信息进行总结。这个方法作用就是解析时候将被用@Import注解引入的bean加入到使用@Import注解标签的bean的importedBy中后面进行解析时候用,还有就是后面注册bean的时候可能也会调用实现了ImportBeanDefinitionRegistrar类型的子类。

2.3.2 processConfigurationClass方法处理configClass

 上面processImports方法调用之后最后都会返回到processConfigurationClass。这里介绍以下这个方法的作用。这个方法会对ConfigurationClass对象(这个对象封装了贴了@Configuration或者@Bean注解的对象的信息)进行解析,解析上面的注解。就我们上面提到的@Component@PropertySources@ComponentScans等。最后保存起来,后面实例化的时候会用到(后面实例化的时候使用的是CGLIB的方式实例化的),跟其他的bean不一样。

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        ......
        do {
            //处理configClass
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);
        //保存到configurationClasses中
        this.configurationClasses.put(configClass, configClass);
    }

 到这里@Import注解的解析基本就完了。剩下的就是介绍ImportSelectorImportBeanDefinitionRegistrar以及DeferredImportSelector之间的区别了。下篇文章解析。

3.ImportSelectorImportBeanDefinitionRegistrar以及DeferredImportSelector

 在上面的processImports方法中已经讲解了对所有的@Import注解中value值为不同指的情况进行解析。有以下的情况:

  1. ImportSelector接口的实现类
  2. DeferredImportSelector接口的实现类
  3. ImportBeanDefinitionRegistrar接口的实现类
  4. 非以上3中接口的实现类,也就是普通的类

 这里主要讲解1到3这三个接口类的区别。

3.1 ImportSelector
public interface ImportSelector {

    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

ImportSelector接口作用将方法返回的字符串数组作为bean注入到容器中,注意这里的字符串需要是对象的全路径名称比如A.class.getName()这种。后面会讲spring的扩展的时候会使用的。

3.2 ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar这个接口作用是,用户可以实现了之后来自定义来注册需要注册的bean。可以设置自定义的BeanNameGeneratorbean名称生成规则。

public interface ImportBeanDefinitionRegistrar {

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
            BeanNameGenerator importBeanNameGenerator) {

        registerBeanDefinitions(importingClassMetadata, registry);
    }

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }

}
3.3 DeferredImportSelector

ImportBeanDefinitionRegistrarImportSelector的子接口,在4.0版本加入的。在这个类里面添加分组的功能,能够将多个DeferredImportSelector进行分组。同一个组内的ImportBeanDefinitionRegistrar能够通过实现@Order注解去实现排序。在调用的时候处理的时候会先按照序号进行排序,然后依次调用对应实现的 ImportSelector接口的selectImports方法。
 还有一点就是DeferredImportSelector的调用逻辑在,所有的@Configuration已经解析了之后在调用的。这点可以在2.2.4 ConfigurationClassParser的解析过程代码中看出来。

public interface DeferredImportSelector extends ImportSelector {

    default Class<? extends Group> getImportGroup() {
        return null;
    }

    interface Group {

    ......

    }

 这里将区别列举出来

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

推荐阅读更多精彩内容