Spring源码分析(一) 从Spring Bean的生命周期谈起

Spring Bean的生命周期真的是面试的时候关于Spring的最高频的考点之一了,笔者曾经被这个问题问懵了不止一次,一直记不住那一大串的步骤,但实际上尝试去死记硬背那些步骤的我是错误的,表面上看只是背诵一个流程,实际上,这个流程牵扯到的知识点可是很多而且很有意思的。

下面这个图我想很多人应该都看过相同的或者相似的:

Spring Bean的生命周期

看起来还是挺长的对吧,但是我们其实可以把它划分成下面四个大的步骤:

  1. 实例化Bean
  2. 设置对象属性,依赖注入
  3. 初始化
  4. destroy:bean的销毁

接下来,我会根据这个步骤,一步步的讲解相关的知识点,从生命周期出发,将Spring中重要的类或接口来一一说明。

一、实例化Bean

这第一步,容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入,可以理解成new xx()。但要注意的是,对于BeanFactory容器和ApplicationContext容器它们的实例化是有区别的。

  • 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean()进行实例化。
  • 对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。

那么既然提到了BeanFactory和ApplicationContext,我就对这两个接口也简单介绍一下。它们分别位于org.springframework.beansorg.springframework.context两个包下,而这两个包,正是Spring IoC容器的基础。BeanFactory提供了框架最基础的一些功能,包括IOC的配置机制,提供Bean的各种定义,建立Bean之间的依赖关系等。ApplicationContext继承了BeanFactory,因此它拥有BeanFactory的所有功能,但它本身还继承了一些其他的接口,比如消息资源配置,事件发布等。

BeanFactory是Spring框架的基础设施,面向Spring。ApplicationContext面向Spring框架的开发者。所以我们平常开发的过程中其实也可以发现到,我们更多的会用上的是ApplicationContext相关的工具,而不是BeanFactory。

除此之外,在实例化这个步骤的前后,实际上还有隐藏任务,牵扯到的接口叫做InstantiationAwareBeanPostProcessor。它继承了BeanPostProcessor。可能有的人不知道BeanPostProcessor这个接口,那么我就先介绍下这个。

BeanPostProcessor也是位于org.springframework.beans下,又叫做后置处理器,它定义了一系列的回调方法用来让使用者可以自定义Bean实例化或者依赖解析等的逻辑。它本身只有两个方法: postProcessBeforeInitialization和postProcessAfterInitialization。如方法名所说,定义Bean的初始化前后的逻辑。这个接口在整个生命周期中都有着关联。

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

不要弄混了BeanPostProcessor中的方法我说的是初始化前后,而生命周期的第一步是实例化。这里用到的InstantiationAwareBeanPostProcessor它有自己的对于实例化逻辑处理的两个方法。postProcessBeforeInstantiation和postProcessAfterInstantiation。

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }

    default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }

    @Nullable
    default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        return null;
    }
}

通过查找调用postProcessBeforeInstantiation这个方法的地方,会追溯到创建Bean的关键方法。就是AbstractAutowireCapableBeanFactory下的createBean()。

/**
 * Central method of this class: creates a bean instance,
 * populates the bean instance, applies post-processors, etc.
 * @see #doCreateBean
 */
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {
    // 里面的代码我下面挑出重点的步骤一步步的贴出来
}

通过查看源码对于此方法的注释就知道它有多重要了。它可以创建Bean的实例,对其进行属性填充,调用BeanPostProcesser等。第一步我们注意到的代码应该是

Class<?> resolvedClass = resolveBeanClass(mbd, beanName);

它实际上是比我们上面生命周期的图示还要之前的一步,就是获取BeanDefinition对象中的信息来对Bean class进行解析。所谓BeanDefinition,它就是对于Bean的一个描述。它包含了Bean的一些基础信息,如Bean的name、Bean的作用域、和其他Bean的关系等等。解析完之后再进行到下一步。

// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
  return bean;
}

这个注释其实也给出了它的意义了,它给予BeanPostProcessor们一个机会,可以返回一个代理而不是目标bean的实例。但我们翻阅到上面的InstantiationAwareBeanPostProcessor对于此方法的实现,会发现它返回的是null,所以默认的逻辑这里是不会return的。所以我们会走到下一步,doCreateBean。

Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
   logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;

进入到这个方法,它的第一行注释也是简单粗暴,就是来实例化Bean的。

// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
   instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
   instanceWrapper = createBeanInstance(beanName, mbd, args);
}

但可以发现,createBeanInstance()这个方法返回的是一个BeanWrapper对象。BeanWrapper是对Bean的一个包装,它可以设置获取被包装的对象,获取被包装bean的属性描述器等。我们平时开发是不会直接使用这个类的。通过createBeanInstance()这个方法的注释我们也能明白,它的作用是对于指定的bean生成一个新的实例,这里可以使用合适的实例化策略,比如工厂方法,构造器注入或者简单实例化等。

    /**
     * Create a new instance for the specified bean, using an appropriate instantiation strategy:
     * factory method, constructor autowiring, or simple instantiation.
     */
    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    //代码我也不贴了
  }

接下来这段代码,注释也有说明,就是将调用各种postProcessers来进行属性的合并,这里会进行一些注解的扫描。

// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
   if (!mbd.postProcessed) {
      try {
         applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
      }
      catch (Throwable ex) {
         throw new BeanCreationException(mbd.getResourceDescription(), beanName,
               "Post-processing of merged bean definition failed", ex);
      }
      mbd.postProcessed = true;
   }
}

再然后是一段跟一个面试题紧密相关的代码,那就是Spring如何解决循环依赖。因为本文的重点是讲bean的生命周期,本来准备一起写完的,但写着写着发现要说的太多了,后面我会另外单独写文章来讲述如何解决循环依赖的问题。

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

二、设置对象属性,依赖注入

结束了实例化之后,我们继续在createBean这个方法上继续往下走,到了populateBean这一步。

// Initialize the bean instance.
Object exposedObject = bean;
try {
   populateBean(beanName, mbd, instanceWrapper);
   exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
   if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
      throw (BeanCreationException) ex;
   }
   else {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
   }
}

对于这个方法,我们已开始注意到的就是我之前说过在实例化前后都是存在着隐藏任务,上面提到了实例化之前的postProcessBeforeInstantiation,那么如今已经实例化了,现在出现了实例化之后的方法postProcessAfterInstantiation,这里也是给InstantiationAwareBeanPostProcessors一个机会去自定义属性被赋值后的方式。在后面代码中回去执行这个后置处理器的方法。

// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
// state of the bean before properties are set. This can be used, for example,
// to support styles of field injection.
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
   for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
      if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
         return;
      }
   }
}

继续往下,我们能看到一个熟悉的单词,Autowire,不过这里是获取当前注入的方式,根据byName还是byType来获得要注入的属性。这里我也不深入说明了。

PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

int resolvedAutowireMode = mbd.getResolvedAutowireMode();
if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
   MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
   // Add property values based on autowire by name if applicable.
   if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
      autowireByName(beanName, mbd, bw, newPvs);
   }
   // Add property values based on autowire by type if applicable.
   if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
      autowireByType(beanName, mbd, bw, newPvs);
   }
   pvs = newPvs;
}

然后就是populateBean的第一步我就提到的方法,如果存在后置处理器,那么会进行属性的校验和处理,同时第二个布尔变量是用来判断是否进行依赖校验的。

boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

PropertyDescriptor[] filteredPds = null;
if (hasInstAwareBpps) {
   if (pvs == null) {
      pvs = mbd.getPropertyValues();
   }
   for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
      PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
      if (pvsToUse == null) {
         if (filteredPds == null) {
            filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
         }
         pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
         if (pvsToUse == null) {
            return;
         }
      }
      pvs = pvsToUse;
   }
}
if (needsDepCheck) {
   if (filteredPds == null) {
      filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
   }
   checkDependencies(beanName, mbd, filteredPds, pvs);
}

最后一步,就是将前面这些步骤得到的属性进行注入了。

if (pvs != null) {
   applyPropertyValues(beanName, mbd, bw, pvs);
}

三、初始化

populateBean()方法结束后开始进入initializeBean()方法,正如方法名所表达的,就是bean的初始化。注意初始化和实例化不是一个意思,在生命周期中bean是先进行实例化再进行初始化的。

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
   if (System.getSecurityManager() != null) {
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }
   else {
      invokeAwareMethods(beanName, bean);
   }

   Object wrappedBean = bean;
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }

   try {
      invokeInitMethods(beanName, wrappedBean, mbd);
   }
   catch (Throwable ex) {
      throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
   }
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }

   return wrappedBean;
}

首先第一步,先不管那些安全机制的代码,我们要关注的是invokeAwareMethods(beanName, bean);这行代码,这里就是调用所有的Aware类型的接口。Spring中Aware类型的接口可以让我们获取容器中的一些资源。

private void invokeAwareMethods(String beanName, Object bean) {
   if (bean instanceof Aware) {
      if (bean instanceof BeanNameAware) {
         ((BeanNameAware) bean).setBeanName(beanName);
      }
      if (bean instanceof BeanClassLoaderAware) {
         ClassLoader bcl = getBeanClassLoader();
         if (bcl != null) {
            ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
         }
      }
      if (bean instanceof BeanFactoryAware) {
         ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
      }
   }
}

比如这里面出现的有三种Aware类型的接口: BeanNameAware, BeanClassLoaderAware,BeanFactoryAware。它们根据bean实现了某个接口来完成不同的工作。

  • 如果实现了BeanNameAware接口,则通过setBeanName()方法将beanName设置给这个bean
  • 如果实现了BeanClassLoaderAware接口,则通过setBeanClassLoader()方法,将ClassLoader传入
  • 如果实现了BeanFactoryAware接口,则通过setBeanFactory()方法,将BeanFactory容器实例传入

结束完Aware接口的调用,继续往下走,到了applyBeanPostProcessorsBeforeInitialization()这个方法(因为if判断是这个bean是空或者这个bean是否是synthetic的,就是是否是应用自己定义的,显然我们自己的bean不是,所以if条件后半部分会是true)。这个方法就是处理我之前有提到过的BeanPostProcessor的postProcessBeforeInitialization()方法。如果Bean实现了BeanPostProcessor接口,那么就会执行这个方法

之后,流程来到了invokeInitMethods()这一步,这一步就是执行bean的自定义初始化方法。如果bean实现了InitializingBean接口,就需要重写afterPropertiesSet()方法,则invokeInitMethods的时候会调用这个重写后的afterPropertiesSet()方法,如果bean自定义了init方法,则会调用指定的init方法(可通过xml中配置bean的init-method属性实现),这两个方法的先后顺序是: afterPropertiesSet在前,自定义init在后

继续下去就是applyBeanPostProcessorsAfterInitialization()方法了,和前面的applyBeanPostProcessorsBeforeInitialization()方法前后呼应,这个就不必多说了。

四、销毁

后面的一段代码还是跟处理循环依赖有关系,就先不多说,直接到最后一步,registerDisposableBeanIfNecessary().

销毁这一步其实就是可以让我们自己自定义bean的销毁方法,用到的关键的接口是DisposableBean,它和InitializingBean接口类似,只是一个初始化阶段的init,一个是结束阶段的destroy。

/**
 * Add the given bean to the list of disposable beans in this factory,
 * registering its DisposableBean interface and/or the given destroy method
 * to be called on factory shutdown (if applicable). Only applies to singletons.
 * @param beanName the name of the bean
 * @param bean the bean instance
 * @param mbd the bean definition for the bean
 * @see RootBeanDefinition#isSingleton
 * @see RootBeanDefinition#getDependsOn
 * @see #registerDisposableBean
 * @see #registerDependentBean
 */
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
   AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
   if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
      if (mbd.isSingleton()) {
         // Register a DisposableBean implementation that performs all destruction
         // work for the given bean: DestructionAwareBeanPostProcessors,
         // DisposableBean interface, custom destroy method.
         registerDisposableBean(beanName, new DisposableBeanAdapter(
               bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
      }
      else {
         // A bean with a custom scope...
         Scope scope = this.scopes.get(mbd.getScope());
         if (scope == null) {
            throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
         }
         scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(
               bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
      }
   }
}

代码示例

上面一大段一大段的源码可能看着让人有点难受,不如直接写个demo轻松一下。因为只是表现一下生命周期,那么我们的依赖就很简单,spring-context和spring-beans即可。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

我们先定义一个自定义的后置处理器,为了表现初始化前后和实例化前后的流程,实现InstantiationAwareBeanPostProcessor接口,并重写postProcessBeforeInstantiation()/postProcessAfterInstantiation()/postProcessBeforeInitialization()/postProcessAfterInitialization()这四个方法。

public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        System.out.println(beanName + "实例化前");
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "实例化后");
        return false;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "初始化前");
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "初始化后");
        return null;
    }

}

再定义一个“常规”的Bean,但我们也为了反应初始化和销毁的几个操作,让他实现了InitializingBean, DisposableBean, BeanNameAware三个接口。并实现对应的方法,Aware接口这里我就只实现一个了,只是为了表现注入Aware接口那个步骤。

public class DemoBean implements InitializingBean, DisposableBean, BeanNameAware {

    public DemoBean() {
        System.out.println("demoBean实例化");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("demoBean afterPropertiesSet");
    }

    public void init() {
        System.out.println("demoBean init");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("demoBean destroy");
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("BeanNameAware setBeanName: "+ s);
    }
}

配置文件很简单,就两个bean。

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <beans>
        <bean name="myInstantiationAwareBeanPostProcessor" class="cn.leafw.spring.beans.MyInstantiationAwareBeanPostProcessor" />
        <bean name="demoBean" class="cn.leafw.spring.beans.DemoBean" init-method="init">
        </bean>
    </beans>
</beans>

测试类也很简单粗暴,就单纯启动和结束。

public class Test {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
        applicationContext.registerShutdownHook();
    }
}

点击运行,接下来就是意料之中的结果输出:

测试结果

因为属性注入不方便在这里面进行测试,但大家知道是在实例化和初始化之间就好。所以其实总的来说,Spring的生命周期理解起来不算很难,但大家对生命周期里面关键的接口或者类要有印象,ApplicationContext和BeanFactory就不用多说,BeanPostProcessor是一定要知道的,Spring中的很多功能的实现都跟其有关。比如@Autowired注解的原理,数据校验Validate的原理,都是有对应的处理器。

这篇博客写了我挺久的时间,就是越写发现自己对Spring的了解越浅薄,本以为啃完了Spring文档的我应该是能轻松看懂源码,但实际上还是有点吃力的,但看懂了之后真的神清气爽,后面还会继续在这个坑里多填一点东西,学到的越多发现不会的越多,一起进步吧。

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

推荐阅读更多精彩内容