一文读懂 Spring Bean 的生命周期

欢迎大家关注我的微信公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。

一、前言

今天我们来说一说 Spring Bean 的生命周期,小伙伴们应该在面试中经常遇到,这是正常现象。因为 Spring Bean 的生命周期是除了 IoC、AOP 几个核心概念之外最重要概念,大家务必拿下。可 Spring 源代码又比较复杂,跟着跟着就不知道跟到哪里去了,不太好拿下呀。这倒是真的,而且网上一上来就各种贴流程源码,对初学者来说是真的一脸懵逼,就像字都看的懂,但连在一块就不知道意思了,太绕了。

本文老周试着讲的通俗易懂些,让更多的小伙伴们轻松的读懂 Spring Bean 的生命周期,并有对它有继续研究学习的想法,那我写此文的目的也就达到了。

我们讲 Spring Bean 的生命周期之前先来了解两个概念:

1.1 什么是 Bean

我们来看下 Spring Framework 的官方文档:

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

简而言之,bean 是由 Spring IoC 容器实例化、组装和管理的对象。

1.2 什么是 Spring Bean 的生命周期

对于普通的 Java 对象,当 new 的时候创建对象,然后该对象就能够使用了。一旦该对象不再被使用,则由 Java 自动进行垃圾回收。

而 Spring 中的对象是 bean,bean 和普通的 Java 对象没啥大的区别,只不过 Spring 不再自己去 new 对象了,而是由 IoC 容器去帮助我们实例化对象并且管理它,我们需要哪个对象,去问 IoC 容器要即可。IoC 其实就是解决对象之间的耦合问题,Spring Bean 的生命周期完全由容器控制。

二、Spring Bean 的生命周期

这里老周必须要提一下,这里我们说的 Spring Bean 的生命周期主要指的是 singleton bean,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

我们也来复习下 Spring 中的 bean 的作用域有哪些?

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
  • global-session: 全局 session 作用域,仅仅在基于 Portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

我们知道对于普通的 Java 对象来说,它们的生命周期就是:

  • 实例化
  • 该对象不再被使用时通过垃圾回收机制进行回收

而对于 Spring Bean 的生命周期来说:

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

实例化 -> 属性赋值 -> 初始化 -> 销毁

只有四个步骤,这样拆解的话是不是感觉也不难?不像其他人写的那样直接一上来就各种 BeanPostProcessor、BeanFactoryPostProcessor 全部怼进流程里去,别说读者看着头大,自己写的可能短时间内还记得流程,隔个一段时间,你可能都不知道自己写了个啥。

本来老周想通过 Bean 创建流程入口
AbstractApplicationContext#refresh() 方法的 finishBeanFactoryInitialization(beanFactory) 处带大家跟一下源码,想了想还是不带入过多的代码进来,直接给到最终的主要逻辑。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {    BeanWrapper instanceWrapper = null;    if (mbd.isSingleton()) {        instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);    }    if (instanceWrapper == null) {        // 实例化阶段        instanceWrapper = this.createBeanInstance(beanName, mbd, args);    }    ...    Object exposedObject = bean;    try {        // 属性赋值阶段        this.populateBean(beanName, mbd, instanceWrapper);        // 初始化阶段        exposedObject = this.initializeBean(beanName, exposedObject, mbd);    } catch (Throwable var18) {        ...    }    ...}

至于销毁,是在容器关闭时调用的,详见 ConfigurableApplicationContext#close()

是不是很清爽了?至于 BeanPostProcessor、BeanFactoryPostProcessor 以及其他的类,在老周看来,只不过是对主流程四个步骤的一系列扩展点而已。

三、Spring Bean 的生命周期的扩展点

Spring Bean 的生命周期的扩展点超级多,老周这里不可能全部列出来,只说核心的扩展点。这也就是为什么 Spring 的扩展性很好的原因,开了很多的口子,尽可能让某个功能高内聚松耦合,用户需要哪个功能就用哪个,而不是直接来一个大而全的东西。

3.1 Bean 自身的方法

比如构造函数、getter/setter 以及 init-method 和 destory-method 所指定的方法等,也就对应着上文说的实例化 -> 属性赋值 -> 初始化 -> 销毁四个阶段。

一文读懂 Spring Bean 的生命周期

3.2 容器级的方法(BeanPostProcessor 一系列接口)

主要是后处理器方法,比如下图的 InstantiationAwareBeanPostProcessorBeanPostProcessor 接口方法。这些接口的实现类是独立于 Bean 的,并且会注册到 Spring 容器中。在 Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用。

[图片上传失败...(image-aa054e-1626625745817)]

3.2.1 InstantiationAwareBeanPostProcessor 源码分析

我们翻一下源码发现 InstantiationAwareBeanPostProcessor 是继承了 BeanPostProcessor

在这里插入图片描述
在这里插入图片描述
  • InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 调用点

Object postProcessBeforeInstantiation(Class beanClass, String beanName)
返回值:如果返回的不为null,那么后续的Bean的创建流程【实例化、初始化afterProperties】都不会执行,而是直接使用返回的快捷Bean,此时的正常执行顺序如下:
InstantiationAwareBeanPostProcessor接口中的postProcessBeforeInstantiation,在实例化之前调用。
BeanPostProcessor接口中的postProcessAfterInitialization,在实例化之后调用。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总之,postProcessBeforeInstantiation 在 doCreateBean 之前调用,也就是在 bean 实例化之前调用的,英文源码注释解释道该方法的返回值会替换原本的 Bean 作为代理,这也是 AOP 等功能实现的关键点。

  • InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 调用点

boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException
正常情况下在实例化之后在执行populateBean之前调用
返回值:如果有指定的bean的时候返回false,那么后续的属性填充和属性依赖注入【populateBean】将不会执行,同时后续的postProcessPropertyValues将不会执行,但是初始化和BeanPostProcessor的仍然会执行。

在这里插入图片描述

public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
实例化之后调用,在方法applyPropertyValues【属性填充】之前
返回值:如果返回null,那么将不会进行后续的属性填充,比如依赖注入等,如果返回的pvs额外的添加了属性,那么后续会填充到该类对应的属性中。
pvs:PropertyValues对象,用于封装指定类的对象,简单来说就是PropertyValue的集合,里面相当于以key-value形式存放类的属性和值。
pds:PropertyDescriptor对象数组,PropertyDescriptor相当于存储类的属性,不过可以调用set,get方法设置和获取对应属性的值。

在这里插入图片描述

3.2.2 BeanPostProcessor 源码分析

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

在这里插入图片描述

进入初始化接口:
在这里插入图片描述

我们先来看

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization

在这里插入图片描述
  • 首先获取到所有的后置处理器 getBeanPostProcessors()
  • 在 for 循环中依次调用后置处理器的方法 processor.postProcessBeforeInitialization(result, beanName);
  • 进入 postProcessBeforeInitialization 方法

org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization

在这里插入图片描述

进入 invokeAwareInterfaces(bean); 方法,当前 bean 实现了 ApplicationContextAware 接口。

在这里插入图片描述
  • ApplicationContextAwareProcessor#postProcessBeforeInitialization 首先判断此 bean 是不是各种的Aware,如果是它列举的那几个 Aware 就获取 Bean 工厂的权限,可以向容器中导入相关的上下文环境,目的是为了 Bean 实例能够获取到相关的上下文,如果不是它列举的几个 Aware,那就调用 invokeAwareInterfaces(bean),向容器中添加相关接口的上下文环境。

3.3 工厂后处理器方法(BeanFactoryProcessor 一系列接口)

包括 AspectJWeavingEnablerCustomAutowireConfigurerConfigurationClassPostProcessor 等。这些都是 Spring 框架中已经实现好的 BeanFactoryPostProcessor,用来实现某些特定的功能。

我们知道 Spring IoC 容器初始化的关键环节就在 org.springframework.context.support.AbstractApplicationContext#refresh 方法中 ,容器创建的主体流程都在这个方法里面,这个方法是真的重要!!!

对于工厂后处理器方法老周这里直接带你看 invokeBeanFactoryPostProcessors(beanFactory); 方法,这个方法处理的是 BeanFactoryPostProcessor 接口的 Bean。调用方法如下:

在这里插入图片描述

跟到最重要的方法里去,代码虽长,但逻辑中规中矩。

BeanFactoryPostProcessor:一切处理 BeanFactory 的父接口
BeanDefinitionRegistryPostProcessor:实现了 BeanFactoryPostProcessor 接口的接口

在这里插入图片描述

流程说明:

  • 调用 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(registry) 方法。参数 beanFactoryPostProcessors 传入的优先处理掉。然后获取容器注册的,对于这些 Bean 按照 PriorityOrdered 接口、Ordered、没有排序接口的实例分别进行处理。
  • 调用 BeanFactoryPostProcessor#postProcessBeanFactory(beanFactory) 方法。备注:BeanDefinitionRegistryPostProcessor 属于 BeanFactoryPostProcessor 子接口。先处理属于 BeanDefinitionRegistryPostProcessor 接口实例的 postProcessBeanFactory(beanFactory) 方法,然后获取容器注册的。对于这些 Bean 按照 PriorityOrdered 接口、Ordered、没有排序接口的实例分别进行处理。

3.4 Bean 级生命周期方法

可以理解为 Bean 类直接实现接口的方法,比如 BeanNameAwareBeanFactoryAwareApplicationContextAwareInitializingBeanDisposableBean 等方法,这些方法只对当前 Bean 生效。

3.4.1 Aware 类型的接口

Aware 类型的接口的作用就是让我们能够拿到 Spring 容器中的一些资源。基本都能够见名知意,Aware 之前的名字就是可以拿到什么资源,例如 BeanNameAware 可以拿到 BeanName,以此类推。调用时机需要注意:所有的 Aware 方法都是在初始化阶段之前调用的

Aware 接口众多,这里同样通过分类的方式帮助大家记忆。Aware 接口具体可以分为两组,至于为什么这么分,详见下面的源码分析。如下排列顺序同样也是 Aware 接口的执行顺序,能够见名知意的接口不再解释。

Aware Group1

  • BeanNameAware
  • BeanClassLoaderAware
  • BeanFactoryAware

Aware Group2

  • EnvironmentAware
  • EmbeddedValueResolverAware
    这个知道的人可能不多,实现该接口能够获取 Spring EL 解析器,用户的自定义注解需要支持 SPEL 表达式的时候可以使用,非常方便。
  • ApplicationContextAware(ResourceLoaderAware/ApplicationEventPublisherAware/MessageSourceAware)
    这几个接口可能让人有点懵,实际上这几个接口可以一起记,其返回值实质上都是当前的 ApplicationContext 对象,因为 ApplicationContext 是一个复合接口,如下:
    在这里插入图片描述

Aware 调用时机源码分析

在这里插入图片描述

可以看到并不是所有的 Aware 接口都使用同样的方式调用。Bean××Aware 都是在代码中直接调用的,而 ApplicationContext 相关的 Aware 都是通过 BeanPostProcessor#postProcessBeforeInitialization() 实现的。感兴趣的可以自己看一下 ApplicationContextAwareProcessor 这个类的源码,就是判断当前创建的 Bean 是否实现了相关的 Aware 方法,如果实现了会调用回调方法将资源传递给 Bean。

BeanPostProcessor 的调用时机也能在这里体现,包围住 invokeInitMethods 方法,也就说明了在初始化阶段的前后执行。

关于 Aware 接口的执行顺序,其实只需要记住第一组在第二组执行之前就行了。

3.4.2 生命周期接口

至于剩下的两个生命周期接口就很简单了,实例化和属性赋值都是 Spring 帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段。

  • InitializingBean 对应生命周期的初始化阶段,在上面源码的 invokeInitMethods(beanName, wrappedBean, mbd);方法中调用。
    有一点需要注意,因为 Aware 方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用 Aware 接口获取的资源,这也是我们自定义扩展 Spring 的常用方式。
    除了实现 InitializingBean 接口之外还能通过注解或者 xml 配置的方式指定初始化方法,至于这几种定义方式的调用顺序其实没有必要记。因为这几个方法对应的都是同一个生命周期,只是实现方式不同,我们一般只采用其中一种方式。
  • DisposableBean 类似于 InitializingBean,对应生命周期的销毁阶段,以ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了 DisposableBean 接口的 Bean 然后调用其 destroy() 方法,感兴趣的可以自行跟一下源码。

3.5 Spring Bean 生命周期流程图

在这里插入图片描述

四、常用接口说明

4.1 BeanNameAware

该接口只有一个方法 setBeanName(String name),用来获取 bean 的 id 或者 name

4.2 BeanFactoryAware

该接口只有一个方法 setBeanFactory(BeanFactory beanFactory),用来获取当前环境中的 BeanFactory

4.3 ApplicationContextAware

该接口只有一个方法 setApplicationContext(ApplicationContext applicationContext),用来获取当前环境中的 ApplicationContext

4.4 InitializingBean

该接口只有一个方法 afterPropertiesSet(),在属性注入完成后调用

4.5 DisposableBean

该接口只有一个方法 destroy(),在容器销毁的时候调用,在用户指定的 destroy-method 之前调用

4.6 BeanPostProcessor

该接口有两个方法:

  • postProcessBeforeInitialization(Object bean, String beanName):在初始化之前调用此方法
  • postProcessAfterInitialization(Object bean, String beanName):在初始化之后调用此方法

通过方法签名我们可以知道,我们可以通过 beanName 来筛选出我们需要进行个性化定制的 bean。

4.7 InstantiationAwareBeanPostProcessor

该类是 BeanPostProcessor 的子接口,常用的有如下三个方法:

  • postProcessBeforeInstantiation(Class beanClass, String beanName):在bean实例化之前调用
  • postProcessProperties(PropertyValues pvs, Object bean, String beanName):在bean实例化之后、设置属性前调用
  • postProcessAfterInstantiation(Class beanClass, String beanName):在bean实例化之后调用

五、代码演示

思路:创建一个类 UserBean ,让其实现几个特殊的接口,并分别在接口实现的构造器、接口方法中断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。

5.1 UserBean 类

@Componentpublic class UserBean implements InitializingBean, BeanNameAware, DisposableBean, ApplicationContextAware {    private int id;    private String name;    public UserBean(int id, String name) {        this.id = id;        this.name = name;        System.out.println("2. 调用构造函数");    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;        System.out.println("5. 属性注入 id");    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;        System.out.println("5. 属性注入 name");    }    @Override    public void setBeanName(String name) {        System.out.println("6. 调用 BeanNameAware.setBeanName() 方法");    }    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        UserBean userBean = (UserBean) applicationContext.getBean("userBean");        System.out.println(userBean);        System.out.println("7. 调用 BeanNameAware.setBeanName() 方法");    }    @Override    public void afterPropertiesSet() throws Exception {        System.out.println("9. 调用 InitializingBean.afterPropertiesSet() 方法");    }    public void myInit() {        System.out.println("10. 调用 init-method 方法");    }    @Override    public void destroy() throws Exception {        System.out.println("12. 调用 DisposableBean.destroy() 方法");    }    public void myDestroy() {        System.out.println("13. 调用 destroy-method 方法");    }    @Override    public String toString() {        return "UserBean{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}

5.2 InstantiationAwareBeanPostProcessor 接口实现类

@Componentpublic class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {    @Override    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {        if ("userBean".equals(beanName)) {            System.out.println("1. 调用 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() 方法");        }        return null;    }    @Override    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {        if ("userBean".equals(beanName)) {            UserBean userBean = (UserBean) bean;            System.out.println("3. 调用 InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation() 方法");            System.out.println(userBean);        }        return true;    }    @Override    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {        if ("userBean".equals(beanName)) {            System.out.println("4. 调用 InstantiationAwareBeanPostProcessor.postProcessProperties() 方法");        }        return null;    }}

5.3 BeanPostProcessor 接口实现类

@Componentpublic class MyBeanPostProcessor implements BeanPostProcessor {    @Override    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        if ("userBean".equals(beanName)) {            System.out.println("8. 调用 BeanPostProcessor.postProcessBeforeInitialization() 方法");        }        return bean;    }    @Override    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        if ("userBean".equals(beanName)) {            System.out.println("11. 调用 BeanPostProcessor.postProcessAfterInitialization() 方法");        }        return bean;    }}

5.4 BeanFactoryPostProcessor 接口实现类

@Componentpublic class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        System.out.println("0. 调用 BeanFactoryPostProcessor.postProcessBeanFactory() 方法");    }}

5.5 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"       xsi:schemaLocation="        http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd">    <bean class="com.riemann.test.MyInstantiationAwareBeanPostProcessor" />    <bean id="userBean" class="com.riemann.test.UserBean" init-method="myInit" destroy-method="myDestroy">        <!-- 构造函数注入 -->        <constructor-arg index="0" type="int">            <value>1</value>        </constructor-arg>        <constructor-arg index="1" type="java.lang.String">            <value>微信公众号【老周聊架构】</value>        </constructor-arg>        <!-- setter方法注入 -->        <property name="id" value="2"/>        <property name="name" value="riemann"/>    </bean>    <bean class="com.riemann.test.MyBeanPostProcessor" />    <bean class="com.riemann.test.MyBeanFactoryPostProcessor" /></beans>

5.6 测试类

public class BeanLifeCycleTest {    public static void main(String[] args) {        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");        UserBean user = (UserBean) applicationContext.getBean("userBean");        ((AbstractApplicationContext) applicationContext).close();    }}

5.7 控制台结果打印

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

推荐阅读更多精彩内容