SpringBoot启动原理解析

概述

前面已经介绍过Spring的IOC和AOP原理,那我们再来看我们平时很熟悉的SpringBoot的启动原理是怎么样的。

SpringBoot启动流程

spring boot 启动流程

图片来自: https://www.processon.com/view/link/59812124e4b0de2518b32b6e

创建一个springboot 项目

这里就拿之前写过的springboot demo项目来为例,看一下其启动类DemoApplication

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

简单到一眼就可以让你发现它的关键信息:@SpringBootApplication、SpringApplication.run(DemoApplication.class, args) 在这两个地方。

SpringApplication.run(DemoApplication.class, args)

我们可以跟踪这个run方法进去看看里边的玄机,最终我们会看到一个核心的run方法:

    public ConfigurableApplicationContext run(String... args) {

        //应用启动计时器器StopWatch 对象
        StopWatch stopWatch = new StopWatch();

        //计时开始(后面有个对应的计时结束)
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

            //配置环境信息
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);

            // banner配置和打印
            Banner printedBanner = this.printBanner(environment);
            
            //注释1. 创建ApplicationContext上下文容器
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            //注释2. 容器初始化(很重要的ApplicationContext#refresh就在这里!!!)
            this.refreshContext(context);

            this.afterRefresh(context, applicationArguments);

            //应用启动计时结束, 到这应用启动就结束了
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

注释1:创建ApplicationContext上下文容器
注释2:容器初始化(很重要的ApplicationContext#refresh就在这里!!!)
对于容器的初始化,我们在Spring IOC系列文章中已经讲过了,对于beanFactory的创建,bean定义的注册,beanFactoryPostProcessor的调用,bean的创建和初始化,beanPostProcessor的调用等等,可以回顾一下《Spring源码阅读----Spring IoC之BeanFactory、ApplicationContext》,后面可以参考。
先到这里,我们先来解析注解,后面再回过来展开这里的细节。

@SpringBootApplication注解

@SpringBootApplication注解

可以看到它是一个复合注解,前面四个就不解释了,另外包含了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解。

  • @SpringBootConfiguration
    点开发现它包含了@Configuration注解,@Configuration在Spring中已经很熟悉了是个配置类的注解。
    之前我们在applicationContext.xml中写过配置信息:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <!--bean 定义 -->
    <bean id="testAaService" class="com.zhlab.ssm.demo.web.service.TestAaService">
        <property name="testBbService" ref="testBbService"></property>
    </bean>
    <bean id="testBbService" class="com.zhlab.ssm.demo.web.service.TestBbService">
        <property name="testAaService" ref="testAaService"></property>
    </bean>
</beans>

那我们在springboot里就可以写一个:

@Configuration
public class XxConfig {

    //bean 定义
    @Bean
    public TestBbService testBbService(){
        TestBbService bb =  new TestBbService();
        bb.setTestAaService(testAaService());
        return bb;
    }
    
    @Bean
    public TestAaService testAaService(){
        return new TestAaService();
    }
}

这两种效果是一样的前者为xml配置、后者为JavaConfig的配置形式,TestAaService、TestBbService 两个类在介绍Spring 循环依赖一文中有。

  • @ComponentScan
    字面意思可以看出它是组件扫描,它会描并加载符合条件的组件(比如@Component和@Service等)或者bean定义,最终将这些bean定义加载到IoC容器中
    可以使用basePackages 知道要扫描的路径
    可以使用includeFilters 来过滤要包含的组件
    可以使用excludeFilters 来过滤要排除的组件

  • @EnableAutoConfiguration
    开启自动配置注解。它的作用是通过@Import,将所有符合自动配置条件的bean定义加载到IoC容器中。
    点开它可以发现它包含了:@AutoConfigurationPackage、@Import({AutoConfigurationImportSelector.class})两个注解。我们重点来分析这两个注解。

    1. @AutoConfigurationPackage
      自动配置包,点进去看它包含一个**@Import({Registrar.class}) **注解,将Registrar作为bean导入Spring容器中
      Registrar 是 AutoConfigurationPackages类(名字和上边的接口有点像☺)的一个内部静态类:
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

它实现了ImportBeanDefinitionRegistrar接口,并重写了registerBeanDefinitions方法,其调用AutoConfigurationPackages.register,将其同级包名作为bean定义注册到容器中,这样可以给@ConponentScan作为默认的basePackages来扫描。

其被调用过程如下:

SpringApplication.run()
    => refreshContext
        // 这里web应用的时候创建的是AnnotationConfigServletWebServerApplicationContext
        => AnnotationConfigServletWebServerApplicationContext.refresh()
            //这个比较熟悉吧?调用各个注册了的BeanFactoryPostProcessors
            =>AbstractApplicationContext.invokeBeanFactoryPostProcessors()
              => PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
                //注释1.在这里调用到了ConfigurationClassPostProcessor这个后置处理器
                =>ConfigurationClassPostProcessor.processConfigBeanDefinitions()
                  =>ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()
                    //遍历执行实现了ImportBeanDefinitionRegistrar接口的实例的registerBeanDefinitions方法
                    =>loadBeanDefinitionsFromRegistrars()
                      //注释2.调用到Registrar类的registerBeanDefinitions
                      =>AutoConfigurationPackages$Registrar.registerBeanDefinitions()
                        =>AutoConfigurationPackages.register()

注释1. 这里用到了ConfigurationClassPostProcessor后置处理器,这个类比较强大,用于启动过程中对配置类的发现和处理,注册bean定义。
注释2.调用到Registrar类的registerBeanDefinitions方法,其调用了AutoConfigurationPackages.register方法
register方法源码如下:

    //定义Bean的名称
    private static final String BEAN = AutoConfigurationPackages.class.getName();

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {

        //该bean是否已经注册
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);

            //将要注册包名称添加进去
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2);

            //注册bean
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }

    }

这里的BeanDefinitionRegistry 对象registry 其实是DefaultListableBeanFactory实例对象,对于DefaultListableBeanFactory类应该比较熟悉了吧。
所以,SpringBoot是通过这种方式,自动地扫描并注册了启动类同级以及子包下的组件bean定义,一般会把启动类放在根目录下。

  1. @Import({AutoConfigurationImportSelector.class})
    接下来看这个注解,导入了AutoConfigurationImportSelector
    查看AutoConfigurationImportSelector类的类图,如下:
AutoConfigurationImportSelector

其实现了ImportSelector接口,重新了selectImports方法,其源码如下:

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            // 获取自动配置的信息
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

其被调用过程如下:

SpringApplication.run()
    => refreshContext
        => AnnotationConfigServletWebServerApplicationContext.refresh()
            =>AbstractApplicationContext.invokeBeanFactoryPostProcessors()
              => PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
                //注释1. ConfigurationClassPostProcessor后置处理器被调用了
                =>ConfigurationClassPostProcessor.processConfigBeanDefinitions()
                  //解析启动类
                  =>ConfigurationClassParser.parse()
                    =>parse()
                      //遍历查找configClass,并处理配置类
                      =>processConfigurationClass()
                        //真正做事的,处理configClass
                        =>doProcessConfigurationClass()
                          //处理这些improt的类,这里有个getImports()是获取import标签里的值
                          =>processImports()
                            //处理实现了DeferredImportSelector接口的类
                            //AutoConfigurationImportSelector实现了这接口,添加到deferredImportSelectors列表中
                            =>ConfigurationClassParser.DeferredImportSelectorHandler.handle()
                    //注释2.遍历deferredImportSelectors列表中的ImportSelector类,处理并注册搜索到的bean
                    //AutoConfigurationImportSelector类就是在这里被调用到并执行selectImports方法
                    =>ConfigurationClassParser.DeferredImportSelectorHandler.process()

注释1. ConfigurationClassPostProcessor后置处理器被调用了,ConfigurationClassPostProcessor比较重要,本文关注的是SpringBoot启动过程,这个类后面增加一篇详细解析一下
注释2. 我们需要关注的是AutoConfigurationImportSelector类被调用,这里就是它被调用并加载各类配置类的地方,它的selectImports方法被执行了。

知道了被调用的过程,我们继续跟踪getAutoConfigurationEntry方法,查看源码:

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
          
            //注释1.自动配置所需的配置信息在这里获取
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);

            //最后会包装成其内部类AutoConfigurationEntry对象返回
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

注释1.通过getCandidateConfigurations获取自动配置的各种配置类

继续跟踪getCandidateConfigurations方法:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

        //注释. 这里加载自动配置相关的类
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

注释. getSpringFactoriesLoaderFactoryClass获取的是EnableAutoConfiguration类,所以给SpringFactoriesLoader.loadFactoryNames方法传入的参数为EnableAutoConfiguration类,
SpringFactoriesLoader.loadFactoryNames,这里跟进去可以发现它是加载一个文件"META-INF/spring.factories",这个文件在哪呢,里边都有些什么内容?如下图所示:

spring.factories
spring.factories

可以发现这里有很多自动配置的类,可以自行点开其中一些类,可以发现它们都是带@Configuration注解的配置类,根据传入的类名org.springframework.boot.autoconfigure.EnableAutoConfiguration,会根据这个匹配这个key以下的配置类列表,返回这个配置类列表。

加载完这些,最终会回到ConfigurationClassPostProcessor类中,被loadBeanDefinitions方法执行,注册成bean定义。
最后容器完成这些bean的实例化初始化,容器刷新完成,并启动。

小结

观察整个启动过程,最重要的就是检索加载各个组件bean到Spring容器中的过程,
@Component(@Configuration、@Controller、@Service等也都是特殊的@Component),

@SpringBootApplication由三个组成:@SpringBootConfiguration、@EnableAutoConfiguration
、@ComponentScan

  • @SpringBootConfiguration其也是一个@Configuration,它会把启动类作为一个配置类加到Spring容器中
  • @ComponentScan会检索各类组件(@Component)加到Spring容器中
  • @EnableAutoConfiguration由两个组成:@AutoConfigurationPackage(主要为@Import({Registrar.class}))、@Import({AutoConfigurationImportSelector.class})
    1. @AutoConfigurationPackage——@Import({Registrar.class}),会把启动类所在目录及其子包作为bean定义注册,供@ComponentScan自动扫描这些包下的组件。
    2. @Import({AutoConfigurationImportSelector.class}),其AutoConfigurationImportSelector实现了很多接口,重要的是DeferredImportSelector接口,它继承了ImportSelector接口,所以重写selectImports方法,会通过SpringFactoriesLoader类来加载"META-INF/spring.factories"文件,匹配"org.springframework.boot.autoconfigure.EnableAutoConfiguration"的列表会被反射成实例,这些都是@Configuration配置类,然后加入到Spring容器中。

核心的分析完毕,我们在关注一下Tomcat。

Tomcat

我们在使用SpringBoot的时候,可以在其里边直接启动内嵌的Tomcat,这是在什么地方实现的?

在之前分析refresh的时候,其中有个onRefresh方法,它是个模板方法,交给子类自己实现的。
AnnotationConfigServletWebServerApplicationContext 继承自ServletWebServerApplicationContext,在ServletWebServerApplicationContext有个onRefresh方法的实现

    protected void onRefresh() {
        super.onRefresh();

        try {

            //这里创建web服务
            this.createWebServer();

        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {

            //这里获取ServletWebServerFactory ,这里会获取TomcatServletWebServerFactory
            //实现这个接口的还有UndertowServletWebServerFactory、JettyServletWebServerFactory
            ServletWebServerFactory factory = this.getWebServerFactory();

            //注释1.从TomcatServletWebServerFactory获取WebServer
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var4) {
                throw new ApplicationContextException("Cannot initialize servlet context", var4);
            }
        }

        this.initPropertySources();
    }

注释1.从TomcatServletWebServerFactory获取WebServer
来TomcatServletWebServerFactory类中看一下getWebServer方法源码:

    public WebServer getWebServer(ServletContextInitializer... initializers) {

        //创建一个Tomcat对象,然后下面开始设置配置信息(以后可以阅读一下Tomcat的源码)
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());

        //构建一个Connector对象,传入协议值:org.apache.coyote.http11.Http11NioProtocol
        //如果要修改其默认值,需要借助BeanFactoryPostProcessor接口的postProcessBeanFactory
        //这样可以通过获取TomcatServletWebServerFactory对象来修改其additionalTomcatConnectors值
        Connector connector = new Connector(this.protocol);

        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);

        //注释1. 获取TomcatWebServer实例
        return this.getTomcatWebServer(tomcat);
    }

注释1. 获取TomcatWebServer实例,传入参数为tomcat

继续跟踪getTomcatWebServer方法:

    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        //创建TomcatWebServer对象
        return new TomcatWebServer(tomcat, this.getPort() >= 0);
    }

继续到TomcatWebServer类中看看其TomcatWebServer构造方法:

    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        //设置各类属性值
        this.monitor = new Object();
        this.serviceConnectors = new HashMap();
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;

        //开始初始化
        this.initialize();
    }

    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
        synchronized(this.monitor) {
            try {

                //InstanceId加到EngineName中
                this.addInstanceIdToEngineName();
                Context context = this.findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                        this.removeServiceConnectors();
                    }

                });

                //注释1. tomcat服务启动监听,对服务生命周期的监听,对应的还有个服务结束的监听器
                this.tomcat.start();
                this.rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
                } catch (NamingException var5) {

                }

                //Tomcat线程都是守护线程。我们创建一个阻塞非守护线程来避免立即关闭
                this.startDaemonAwaitThread();
            } catch (Exception var6) {
                this.stopSilently();
                throw new WebServerException("Unable to start embedded Tomcat", var6);
            }

        }
    }

注释1.在这里开始对tomcat服务启动的监听,对应的还有个对服务结束的监听。tomcat的server属性因为其类型Server 继承了Lifecycle接口,会对服务的生命周期进行监听,start() 、stop()来自Lifecycle接口。

那在哪里要启动TomcatWebServer服务了呢?在refresh流程中,容器创建的最后一个步骤就是finishRefresh(不考虑resetCommonCaches方法哈),我们来看ServletWebServerApplicationContext的finishRefresh方法:

    protected void finishRefresh() {
        //执行父类AbstractApplicationContext的finishRefresh方法
        super.finishRefresh();
        
        //注释1. 启动了WebServer 
        WebServer webServer = this.startWebServer();
        if (webServer != null) {
            this.publishEvent(new ServletWebServerInitializedEvent(webServer, this));
        }

    }


    private WebServer startWebServer() {
        WebServer webServer = this.webServer;
        if (webServer != null) {
            //注释2. 这里就启动了web服务,调用TomcatWebServer的start方法
            webServer.start();
        }

        return webServer;
    }

注释1. 启动了WebServer
注释2. 这里就启动了web服务,调用TomcatWebServer的start方法

所以,到这里内嵌的tomcat服务就这样在容器初始化的最后阶段启动了。有start当然对应也会有stop停止的方法,具体的Tomcat相关的源码就不在这里展开了,以后阅读Tomcat的源码时再展开做笔记^^。

对于事件驱动的部分,可以参考之前Spring Ioc部分介绍的Spring 事件监听机制。

总结

SpringBoot启动原理的解析就讲到这里,最重要的还是其自动配置的部分,然后也了解了内嵌的Tomcat服务是如何被启动的。

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

推荐阅读更多精彩内容