SpringBoot2.0深度实践学习笔记(五)之 SpringApplication(四)

SpringApplication运行阶段

  • 加载:SpringApplication 运行监听器
  • 运行:SpringApplication 运行监听器
  • 监听:Spring Boot事件、Spring事件
  • 创建:应用上下文、Environment、其他
  • 失败:故障分析报告
  • 回调:CommandLineRunner、ApplicationRunner

具体:

(一)加载:SpringApplication 运行监听(SpringApplicationRunListeners)
利用 Spring 工厂加载机制,读取 SpringApplicationRunListener 对象集合,并且封装到组合类
SpringApplicationRunListeners

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

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

说明:
1)getRunListeners(args);//运行监听器
可以看到工厂加载机制getSpringFactoriesInstances,把实例列表放进来了;
同时SpringApplicationRunListeners
这个对象在设计模式里称为组合对象
当在实现某个接口的时候通过foreach的方式执行。
SpringApplicationRunListeners里面有很多方法,都定义在SpringApplicationRunListener接口里

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
    }

    SpringApplicationRunListeners(Log log,
            Collection<? extends SpringApplicationRunListener> listeners) {
        this.log = log;
        this.listeners = new ArrayList<>(listeners);
    }

(二)运行:SpringApplication 运行监听器
SpringApplicationRunListener 监听多个运行状态方法:

  • starting() : Spring 应用刚启动
  • environmentPrepared(ConfigurableEnvironment) : ConfigurableEnvironment 准备妥当,允许将其调整
  • contextPrepared(ConfigurableApplicationContext):ConfigurableApplicationContext 准备妥当,允许将其调整
  • contextLoaded(ConfigurableApplicationContext):ConfigurableApplicationContext 已装载,但仍未启动
  • started(ConfigurableApplicationContext): ConfigurableApplicationContext 已启动,此时 Spring Bean 已初始化完成
  • running(ConfigurableApplicationContext) :Spring 应用正在运行
  • failed(ConfigurableApplicationContext,Throwable) :Spring 应用运行失败

(三)监听:Spring Boot事件、Spring事件
Spring Boot 通过 SpringApplicationRunListener 的实现类 EventPublishingRunListener 利用 Spring Framework 事件
API ,广播 Spring Boot 事件。

spring.factories
EventPublishingRunListener是SpringApplicationRunListener唯一实现
它把接口都实现了一遍,从EventPublishingRunListener源码中看到都是和事件相关联

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener监听方法与SpringBoot事件对应关系

  • starting() :ApplicationStartingEvent 1.5
  • environmentPrepared(ConfigurableEnvironment):ApplicationEnvironmentPreparedEvent 1.0
  • contextPrepared(ConfigurableApplicationContext)
  • contextLoaded(ConfigurableApplicationContext): ApplicationPreparedEvent 1.0
  • started(ConfigurableApplicationContext) ApplicationStartedEvent 2.0
  • running(ConfigurableApplicationContext): ApplicationReadyEvent 2.0
  • failed(ConfigurableApplicationContext,Throwable) :ApplicationFailedEvent 1.0

EventPublishingRunListener源码
有一个Multicaster广播器,会广播一个事件multicastEvent。
每个方法调用都会广播不同的事件如ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationContextInitializedEvent、ApplicationPreparedEvent、ApplicationStartedEvent、ApplicationReadyEvent

@Override
    public void starting() {
        this.initialMulticaster.multicastEvent(
                new ApplicationStartingEvent(this.application, this.args));
    }

@Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(
                this.application, this.args, context));
    }
……

SimpleApplicationEventMulticaster:简单事件应用广播器,这个广播器在不同的回调方法里做广播.

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

这些事件是Spring Framework 的事件扩展。

监听Spring事件

1、Spring Framework 事件/监听器编程模型

  • Spring 应用事件
    普通应用事件: ApplicationEvent 基类
    应用上下文事件: ApplicationContextEvent
  • Spring 应用监听器
    接口编程模型: ApplicationListener
    注解编程模型: @EventListener
  • Spring 应用事广播器
    接口: ApplicationEventMulticaster
    实现类: SimpleApplicationEventMulticaster
    执行模式:同步或异步

说明:
同步或异步
SimpleApplicationEventMulticaster 里面定义了taskExecutor(并发的时候,线程池的执行器)
在广播事件的时候multicastEvent,当线程池存在的时候,是异步去执行的

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    @Nullable
    private Executor taskExecutor;

    @Nullable
    private ErrorHandler errorHandler;

……

public void setTaskExecutor(@Nullable Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

……

@Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

【栗子1----Spring Framework 事件/监听器】
在spring-application模块下新建一个SpringApplicationEventBootstrap类
(采用SpringFrameWork实现)

/**
 * spring 应用事件引导类
 */
public class SpringApplicationEventBootstrap类 {

    public static void main(String[] args) {
        //创建上下文(注解驱动)
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        //注册应用事件监听器
        context.addApplicationListener(event ->
                System.out.println("监听到事件:" + event));

        //启动上下文
        context.refresh();

        //发送事件
        context.publishEvent("HelloWorld");
        context.publishEvent("2019");

        context.publishEvent(new ApplicationEvent("PK") {
        });


        //关闭上下文
        context.close();

    }
}

运行


1.png

说明:
1)publishEvent---这个接口是发了一个PayloadApplicationEvent事件

2)可以自定义事件
3)springboot实现也是类似的

【栗子2---实现SpringApplication运行监听器】
SpringApplicationRunListener是通过工厂机制加载的
(1)新建一个包run下面新建一个类HelloWorldRunListener,实现接口SpringApplicationRunListener,简单的实现第一个starting()

/**
 * HelloWorld {@link SpringApplicationRunListener}
 */
public class HelloWorldRunListener implements SpringApplicationRunListener {

    public HelloWorldRunListener(SpringApplication application, String[] args){
        
    }
    @Override
    public void starting() {
        System.out.println("HelloWorldRunListener.starting()...");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {

    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {

    }

    @Override
    public void started(ConfigurableApplicationContext context) {

    }

    @Override
    public void running(ConfigurableApplicationContext context) {

    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {

    }
}

注意:
EventPublishingRunListener有2个参数(SpringApplication application, String[] args)
如果你没有添加这2个参数,会报错:构造器没有这个方法:Caused by: java.lang.NoSuchMethodException: com.cbt.diveinspringboot.run.HelloWorldRunListener.<init>
我们看一下源码:
EventPublishingRunListener

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

在SpringApplication.java 里面传了二个参数this, args

private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
    }

getSpringFactoriesInstances,把所有的class都加载起来了,但是初始化的时候是根据构造器初始化的,同时将构造器里面的2个参数传进来

    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;
    }

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
            Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass
                        .getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException(
                        "Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }

(2) 配置spring.factories

# SpringApplicationRunListener实现配置
org.springframework.boot.SpringApplicationRunListener=\
com.cbt.diveinspringboot.run.HelloWorldRunListener

(3)运行之前的引导类SpringApplicationBootstrap
在第一行就输出了


3.png

2、监听SpirngBoot事件
之前看EventPublishingRunListener类源码的时候,它是通过SpringApplicationRunListener回调的方式,发生不同的事件,产生不同效应。

在SpringApplication准备阶段的时候要加载ApplicationListener。
在本工程里spring.factories里面我们已经配置了相关Listener的实现。在构造阶段也已经将Listener装配好了。

当事件发送的时候会把application关联的listerner传进来,通过迭代的方式逐一去执行监听

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

再看一下SimpleApplicationEventMulticaster
实现里的发布事件的时候会迭代的完成。
getApplicationListeners会对事件加以区分

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

当我们不知道实现或者配置在spring工厂加载机制哪里实现的时候,借助IDEA工具find usages,看到非代码的配置(Non-code usages)

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

点击FileEncodingApplicationListener、LoggingApplicationListener上涉及的等等,可以看到他们关心的相关事件,以及顺序。

以上就是Spring Boot事件或者Spring事件应用的情况
特别要注意:监听事件是哪种事件以及顺序。

【栗子----监听SpringBoot事件】
先看一下ConfigFileApplicationListener源码

public class ConfigFileApplicationListener
        implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
……
private int order = DEFAULT_ORDER;
……
@Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }
……
}

说明:
1)ConfigFileApplicationListener关心2个事件,ApplicationEnvironmentPreparedEvent和ApplicationPreparedEvent,事件的执行顺序是ApplicationEnvironmentPreparedEvent是在ApplicationPreparedEvent前面的。

2)ApplicationEnvironmentPreparedEvent会读取application.properties文件。并且顺序要在DEFAULT_ORDER之前,也就是HIGHEST_PRECEDENCE + 10;(表示如果最大优先级第一个执行,它就是第11个执行,如果要比它优先的话+9)

具体操作:
(1)配置application.properties文件

name=PK

(2)在listener包下新建一个类BeforeConfigFileApplicationListener

/**
 * Before {@link ConfigFileApplicationListener} 实现
 */
public class BeforeConfigFileApplicationListener implements SmartApplicationListener,Ordered {

    @Override
    public int getOrder() {
        //比ConfigFileApplicationListener优先级更高
        return ConfigFileApplicationListener.DEFAULT_ORDER +1;
    }

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
                || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
    }

    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return true;
    }


    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {

            ApplicationEnvironmentPreparedEvent preparedEvent = (ApplicationEnvironmentPreparedEvent) event;
           Environment environment =  preparedEvent.getEnvironment();
            System.out.println("environment.getProperty(\"name\") :" +environment.getProperty("name"));

        }
        if (event instanceof ApplicationPreparedEvent) {

        }
    }

说明:
1)实现接口Ordered和SmartApplicationListener
2)执行顺序,这里先采用比源码执行顺序靠前的顺序,所以运行的时候application.properties里的name是读不到的。
3)实现SmartApplicationListener接口的方法参照SmartApplicationListener的源码,进行修改。这里不处理2个事件了,通过PreparedEvent事件可以读到Environment来关联属性。

(3)将内容配置到spring.factories里(最后一个)

# Application Listeners 实现配置
org.springframework.context.ApplicationListener=\
com.cbt.diveinspringboot.listener.AfterHelloWorldApplicationListener,\
com.cbt.diveinspringboot.listener.HelloWorldApplicationListener,\
com.cbt.diveinspringboot.listener.BeforeConfigFileApplicationListener

(4)运行SpringApplicationBootstrap
name为null


4.png

当把执行顺序改为+1,在其之后执行
name就读取到了

5.png

要特别了解springboot的特性,优先级顺序

(四)创建:应用上下文( ConfigurableApplicationContext )
根据准备阶段的推断 Web 应用类型创建对应的 ConfigurableApplicationContext 实例:

  • Web Reactive: AnnotationConfigReactiveWebServerApplicationContext
  • Web Servlet: AnnotationConfigServletWebServerApplicationContext
  • 非 Web: AnnotationConfigApplicationContext

创建 Environment
根据准备阶段的推断 Web 应用类型创建对应的 ConfigurableEnvironment 实例:

  • Web Reactive: StandardEnvironment
  • Web Servlet: StandardServletEnvironment
  • 非 Web: StandardEnvironment

在SpringApplication.run方法里有个createApplicationContext()
根据推断web类型,如果是
根据推断web类型,如果是
SERVLET---DEFAULT_SERVLET_WEB_CONTEXT_CLASS
REACTIVE-----DEFAULT_REACTIVE_WEB_CONTEXT_CLASS
default----DEFAULT_CONTEXT_CLASS

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

SpringApplication.java中的getOrCreateEnvironment()
当 SERVLET的时候就是StandardServletEnvironment,其他的时候就是StandardEnvironment。

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
        }
    }

在SpringApplication的run方法中也间接的表达了

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
        }
    }

再看run方法源码:
在starting的时候什么都没创建,后续开始创建一些,当Environment创建好了,会执行另外一个listener事件(environmentPrepared(environment)),那么ConfigurableEnvironment准备妥当,允许将其调整(参看之前运行SpringApplication运行监听器的监听方法)

然后会遇到createApplicationContext
然后contextPrepared调用就开始了
然后contextLoaded(context)事件开始了

等等,这些都是通过SpringApplication 上下文进行逐一调用的,伴随事件的产生,把相应的资源准备好

我们可以调整这个类型
【栗子】
模块下新建一个类SpringApplicationContextBootstrap

/**
 * Spring应用上下文引导类
 */
@SpringBootApplication
public class SpringApplicationContextBootstrap {

    public static void main(String[] args) {

        ConfigurableApplicationContext context =  new SpringApplicationBuilder(SpringApplicationContextBootstrap.class)
                //.web(WebApplicationType.NONE) //非web类型
                .run(args);

        System.out.println("ConfigurableApplicationContext 类型:" +context.getClass().getName());
 System.out.println("Environment 类型:" +context.getEnvironment().getClass().getName());

        context.close();
    }
}

运行:
1)推断出servlet,并且查看Environment类型的时候:

6.png

2)非web类型的话显示,并且查看Environment类型的时候:就是StandardEnvironment


8.png

3)换成REACTIVE会报错:没有jar包


终于完成了关于SpringApplication的相关学习,里面的东西还是需要结合源码再深度理解,把所有的东西串起来一下。

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

推荐阅读更多精彩内容