逐行阅读Spring5.X源码(一) BeanDefinition,起点

本篇博客你讲学到:
1. 如何理解BeanDefinition
2. 准备环境
3. BeanDefinition接口讲解
4. BeanDefinition的类继承关系
5. IOC的引出
6. BeanFactory工厂的引出
7. 后置处理器的引出
8. spring扩展性初探

1、为什么从BeanDefinition讲起?

       spring源码太庞大了,精通spring源码确实不容易,讲懂更难。学习spring源码是件浩大的工程。很多读者从spring容器启动开始逐行debug源码进行分析,刚开始很容易理解,但是当你逐层debug源码深处时,就会感慨“身在此山中,不识真面目”。笔者在刚开始的时候也经历过这种痛苦,精读几遍之后笔者觉着spring源码不能从头读起,我们需要先搞懂spring中的基础组件及组件之间的关系,这就好比组装电脑,你得先了解CPU作用、内存的作用、主板的作用、硬盘的作用,然后你才知道如何讲他们组装到一起,从头阅读源码就跟你让一个学金融专业的学生组装电脑一样,效果可想而知。

image

       如果我问你spring的作用?想必大部分程序员都会回答“管理对象,方便开发”。中国不缺程序员,缺的是有思想有深度的工程师,别人写的叫程序,你写的那叫BUG。不错,java一切皆对象,spring封装管理你所创建的对象,封装后的对象叫bean,bean扩展了你的对象,你所创建的对象的所有属性和方法是bean中的一个子集。spring所有的功能都是围绕这个bean展开的。这个bean在spring中具体的名字就是BeanDefinition。

怎么理解BeanDefinition?

       假如你编写一个Person.java文件,编译成Persion.class文件后,jvm会将这个class文件加载到虚拟机内存(方法区),程序在当遇到new关键字的时候根据class在堆内生成对象。class文件已经生成了,spring无法更改你的class,但还要扩展你的class怎么办?很简单,spring定义一个BeanDefinition类进一步封装Person类, 最后注册到spring容器中。 所以, Person只是BeanDefinition中的一个属性,就这么简单。BeanDefinition中还会封装跟Person相关的其他属性,比如bean的作用域,bean的注入模型,bean是否是懒加载等等信息。BeaDefiniton结构类似于:

image

对象和bean的对比如下图,编译对应封装,加载对应注册。Person.class是JVM的class,而BeanDefinition就是Spring中的class。专业一点讲,在JDK中使用java.lang.Class来描述类这个对象,Spring使用BeanDefinition来描述bean。

image

       如果你不了解对象这个概念,那你就写不了java程序。同理,如果你不了解BeanDefinition你就读不懂Spring源码。这就是笔者为什么先讲解BeanDefinition。很多读者都下过决心精读spring源码,刚开始热情高涨,读着读着就不耐烦了,什么乱七八糟的不读了,感觉会用就行,你跟架构师就这么越来越远了。我觉着是方法不对。那就跟随笔者的脚步,逐行分析spring源码,毕竟这是你迈向java架构师必须逾越的鸿沟!
在开始之前说清楚,beanDefintion的比较枯燥和晦涩难懂,但是非常非常重要。如果你想精读spring源码,请你一定细读三遍beanDefintion的知识,他是spring framework当中的基石,你避不开!

2、准备环境

  • 开发工具
    IDEA + MAVEN + JDK1.8
  • 导入依赖
 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
    </dependencies>
  • 配置类
//包扫描
@ComponentScan("com")
//@ImportResource("applicationContext.xml")//使用xml文件而非注解的方式进行启动
public class Config {
}
  • xml配置文件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 http://www.springframework.org/schema/beans/spring-beans.xsd"
       >
    <!--注意上面的default-autowire="byType"  根据type进行自动注入-->
    <bean id="ab" abstract="true" ></bean>
    <bean id="xxx" class="com.InterService" parent="ab"></bean>
</beans>

xml方式启动属实用的不多了已经,xml最大的作用就是依赖注入(以后再将,可忽略),笔者在后续讲解中基本以注解方式进行讲解。

  • 业务类
@Component
@Description("业务类")
@Scope("singleton")
public class InterService {
    private String name;
    private int age;
}
  • 测试类
public class SpringTest {
    public static void main(String[] args) throws InterruptedException {
       //以下三行代码完成了spring的启动
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        //加载或者刷新当前的配置信息
        context.refresh();;
    }
}

以上代码均放在com包下。

3、BeanDefinition讲解

       spring启动会完成很多工作,不可能在专题第一篇就把整个启动流程讲清楚,为了引出BeanDefinition,笔者讲解三个过程:

  • 扫描
           spring首先根据Config 的注解@ComponentScan("com")扫描com包下符合规则的类,这里只有InterService类;
  • 解析
           解析InterService类,主要是拿到所有的注解@Component、@Description("业务类")、@Scope("singleton")
  • 封装
           解析完的信息保存到哪里里去呢?就存在BeanDefinition中。不然存哪里?存InterService类中吗?此时回过头读读文章第一节“为什么从BeanDefinition讲起”就知道BeanDefinition的重要性了吧。BeanDefinition就是对你的InterService进行建模。

       其实,BeanDefinition只是一个接口:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
     //InterService的scope值,单例
    String SCOPE_SINGLETON = "singleton";
    //InterService的scope值,非单例
    String SCOPE_PROTOTYPE = "prototype";
    //Bean角色:
    //用户
    int ROLE_APPLICATION = 0;
   //某些复杂的配置
    int ROLE_SUPPORT = 1;
  //完全内部使用
    int ROLE_INFRASTRUCTURE = 2;
    //返回此InterService的的父bean定义的名称,如果有的话 <bean parent="">(xml配置文件中的bean配置)
    String getParentName();    
    void setParentName(String var1);
   
  //设置或获取业务类全路径名
    void setBeanClassName(String var1);
    String getBeanClassName();
    //InterService的scope值
    void setScope(String var1);
    String getScope();
   //InterService的懒加载设置及获取
    void setLazyInit(boolean var1);
    boolean isLazyInit();
    //InterService的依赖对象 ,表示InterService实例创建前应该先创建哪个对象
    void setDependsOn(String... var1);
    String[] getDependsOn();
    //是否为被自动装配
    void setAutowireCandidate(boolean var1);
    boolean isAutowireCandidate();
   //如果是被自动装配,是否为主候选bean
    void setPrimary(boolean var1);
    boolean isPrimary();
   //定义创建该Bean对象的工厂类 
    void setFactoryBeanName(String var1);
    String getFactoryBeanName();
  //定义创建该Bean对象的工厂方法 
    void setFactoryMethodName(String var1);
    String getFactoryMethodName();
  //返回此InterService的构造函数参数值。
    ConstructorArgumentValues getConstructorArgumentValues();
  //获取普通属性集合
    MutablePropertyValues getPropertyValues();
 //是否为单例
    boolean isSingleton();
    //是否为原型
    boolean isPrototype();
    //是否为抽象类
    boolean isAbstract();
  //获取这个bean的应用
    int getRole();
   //返回对bean定义的可读描述。
    String getDescription();
  //返回该bean定义来自的资源的描述(用于在出现错误时显示上下文)
    String getResourceDescription();
    BeanDefinition getOriginatingBeanDefinition();
}

       通过BeanDefinition接口可以对InterService的附加属性进行设置和获取,虽然目前不能完全搞懂所有属性及方法,但笔者仍然建议读者把这个接口代码都看一遍。在这里建议大家读源码一定要“慢下来”,一点一点的啃,切忌浮躁,这个痛苦的过程过去了你会有种高屋建瓴的感觉!那感觉,指点江山!
       光读不行,还得写代码调试从而增加印象,比如上面BeanDefinition接口中的方法我想验证一下,很简单,上代码:

public class SpringTest {
    public static void main(String[] args) throws InterruptedException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        //加载或者刷新当前的配置信息
        context.refresh();;
       //获取InterService对应的BeanDefinition ,默认名称为interService,关于名字的更改以后讲。
        BeanDefinition interServiceBeanDefinition = context.getBeanDefinition("interService");

        System.out.println("——————InterService的附加属性如下:");
        System.out.println("父类"+interServiceBeanDefinition.getParentName());
        System.out.println("描述"+interServiceBeanDefinition.getDescription());
        System.out.println("InterService在spring的名称"+interServiceBeanDefinition.getBeanClassName());
        System.out.println("实例范围"+interServiceBeanDefinition.getScope());
        System.out.println("是否是懒加载"+interServiceBeanDefinition.isLazyInit());
        System.out.println("是否是抽象类"+interServiceBeanDefinition.isAbstract());
        System.out.println("——————等等等等,读者自行编写");
    }
}

打印结果:


image.png

注意,这些属性在xml配置文件中都能找到对应匹配的标签,读者可用xml配置文件自行进行配置,只需要将Config配置类上的@ImportResource("applicationContext.xml")注解打开即可。

       既然BeanDefinition是个接口,那spring中肯定有他的实现类对不对,好,是时候看一下BeanDefinition的类继承图了。在这里笔者跟大家说一个问题,笔者发现很多人读源码的时候拿来一个类就读或者debug源码跟读,也不管这个类跟其他类的关系,读完后感觉很混乱,甚至吐槽spring源码写的毫无章法,拜托,spring源码是典型的面向接口编程,严格遵循开闭原则、依赖倒置原则和迪米特法则(软件设计7大基本原则,大家自行百度啦),是spring源码写的差还是你的水平差?spring每一个模块都有一个完整的类继承关系图,不然spring被业界称赞的高扩展性谈何而来?所以我们必须将每个模块的类继承关系了然于胸。初学,我们也不可能将继承体系中的每个类都搞懂,把这个继承图下载下来存到桌面,在以后的源码阅读中这个继承关系会被你一一攻破,学完你也就掌握了,而且不会忘,更能提高你的编程水平,读完spring你会发现的编程风格潜移默化的被spring影响了!对吧。

BeanDefinition的继承关系:

image.png

       突然间好想放弃有没有,像这种继承图,spring中太多了,我说了学习spring是项浩大的工程,现在放弃还来得及!说实话笔者文章写到这里停了好久,不知道从哪下手讲!

       以前利用spring开发大都通过xml方式进行bean配置,其实spring框架开发之初并不想让程序员通过xml方式进行配置,而是通过代码让我们手动将InterService封装成对应的BeanDefinition,看下面的代码:

//首先,删掉业务类InterService所有的注解,不让他被扫描到(spring根据注解判断是否将类进行包装)
public class InterService {
    private String name;
    private int age;
}
public class SpringTest {
    public static void main(String[] args) throws InterruptedException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName("com.InterService");
        beanDefinition.setScope("singleton");
        beanDefinition.setDescription("手动注入");
        beanDefinition.setAbstract(false);
        //将beanDefinition注册到spring容器中
        context.registerBeanDefinition("interService",beanDefinition);
        //加载或者刷新当前的配置信息
        context.refresh();

        BeanDefinition interServiceBeanDefinition = context.getBeanDefinition("interService");
        System.out.println("——————InterService的附加属性如下:");
        System.out.println("父类"+interServiceBeanDefinition.getParentName());
        System.out.println("描述"+interServiceBeanDefinition.getDescription());
        System.out.println("InterService在spring的名称"+interServiceBeanDefinition.getBeanClassName());
        System.out.println("实例范围"+interServiceBeanDefinition.getScope());
        System.out.println("是否是懒加载"+interServiceBeanDefinition.isLazyInit());
        System.out.println("是否是抽象类"+interServiceBeanDefinition.isAbstract());
        System.out.println("——————等等等等,读者自行编写");
        
    }
}

       其实笔者很喜欢这种代码方式完成spring配置工作,这样能让我们更深入的了解和应用spring,不过这种方式的缺点也很明显-繁琐易出错,spring为了简化我们的工作提供了xml配置方式,直到spring5.x注解方式的稳定成熟,spring全家桶得到了飞速的发展。但通过这个例子读者可以加深对BeanDefinition的理解。

IOC的引出

看上面这么一行代码:

        context.registerBeanDefinition("interService",beanDefinition);

       这行代码的意思是将我们手动封装的beanDefinition注册到容器中,同时给这个beanDefinition起了个名字“interService”,spring内部生成beanDefinitino时会默认起一个名字,改名字的规则就是业务类名字首字母小写。
       那生成的BeanDefinition保存在哪里呢?既然我们是通过上面的方法将BeanDefinition注册到容器中,肯丢是在这个方法底层实现了保存,我们点进去看:

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
        // 将beanDefinition保存到spring容器中
        this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
    }

       继续跟进registerBeanDefinition方法,找到下面这行代码:

                        .....以上代码省略,以后详解
            this.beanDefinitionMap.put(beanName, beanDefinition);

       顾名思义,beanDefinitionMap就是一个Map呀!具体是啥map,我们ctrl+鼠标左键单击找到beanDefinitionMap定义处:

/** Map of bean definition objects, keyed by bean name. */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

       还需要我解释这行代码吗?有人说需要,我不懂ConcurrentHashMap,好吧,这是java并发包java.util.concurrent下的集合类,它就是一个Map,但是支持多线程并发访问,为啥使用ConcurrentHashMap而不是用HashMap,嗨,建议你好好补下java高并发知识(后续我会写一个java高并发编程底层原理,让你吊打面试官,欢迎大家关注)。总之,这就是beanDefinition存储的容器,这行代码所在的类名叫DefaultListableBeanFactory,它是bean的工厂,spring中所有的对象或者说bean都存在这个bean工厂中,业界叫它IOC,很多书或者视频都会讲IOC,相信读者也知道IOC是容器,但它就是一堆Map集合而已,beanDefinitionMap 知识众多Map中的一个而已,以后我会将其他的map容器,今天你只需要只到这么一个存放BeanDefinition的容器即可。
        这下你搞懂IOC了吧! 全体起立!
       有读者问,那DefaultListableBeanFactory这个bean工厂啥时候创建的,我说以后再讲!想必大家都想知道,那就了解一下吧!看代码:

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

       这是我们创建spring上下文对象,AnnotationConfigApplicationContext 类有一个父类,AnnotationConfigApplicationContext的无参 构造函数执行时会默认调用父类无参构造函数(java基础知识),AnnotationConfigApplicationContext 的父类如下:

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

       也就是GenericApplicationContext ,我们看一下他的构造函数:

public GenericApplicationContext() {
        //初始化一个BeanFactory
        this.beanFactory = new DefaultListableBeanFactory();
    }

       我不说话,静静的看着屏幕前的你思考的样子! 也就是说,spring启动的时候就创建好了这个bean工厂!
       咦?不是要将BeanDefinitino的继承关系码?怎么跑偏了?这就是spring的特点,太庞大了,没有孤立的知识点!这也是很多读者阅读spring源码时读着读着就蒙圈的原因。

后置处理器的引出

       上文我们通过手动将InterService封装成了一个BeanDefinition然后注册(说好听了叫注册,起始就是map.put)到了容器中,我说了现在我们没有这么用的了,都是spring自动帮我们完成扫描注册,在哪完成的扫描注册?回到下面这几行代码:

 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        //加载或者刷新当前的配置信息
        context.refresh();

        context.refresh()方法,完成了spring的启动、扫描、解析、实例化等一系列过程,这个方法完成的功能太多了,我们的扫描注册也是在这里完成的,进入到这个方法,找到这么一行代码:

                  ...以上省略
      invokeBeanFactoryPostProcessors(beanFactory);
                  ...以下省略

       翻译一下名字,执行bean工厂的后置处理器,这行代码完成了扫描与注册,我不带大家分析里面的代码,你只需要知道他的作用就行,这行代码执行完成后,我们只是把业务类InterService封装成了BeanDefinition而已,业务类InterService并没有实例化,在业务类InterService实例化之前我们能不能从beanDefinition中将InterService偷梁换柱呢?或者说,我们能通过BeanDefinition来构建bean,那我们能不能修改bean呢?那必须的!
       通过后置处理器完成,什么是后置处理器?可以把它理解成回调,我扫描注册成功后回调后置处理器!BeanDefinition讲完后紧接着就讲后置处理器。我们添加一个后置处理器:

/**
 * 扫描注册成功完成后,spring自动调用后置处理器MyBeanFactoryPostProcessor的postProcessBeanFactory方法
 */
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
        //通过bean工厂拿到业务类InterService的beanDefinition
        GenericBeanDefinition beanDefinition =
                (GenericBeanDefinition) beanFactory.getBeanDefinition("interService");
        System.out.println("扫描注册成功完成后,spring自动调用次方法");
        System.out.println(beanDefinition.getDescription());
    }
}

spring扫描注册完成后,会自动调用MyBeanFactoryPostProcessor的postProcessBeanFactory方法,这个方法给你传递了一个ConfigurableListableBeanFactory类型的bean工厂,ConfigurableListableBeanFactory是一个接口,上文spring启动实例化的DefaultListableBeanFactory工厂是它的实现类。天啊,竟然把bean工厂给你了,相当于敌人把军火库暴露在你面前,你岂不是想干嘛就干嘛!上述代码我们通过bean工厂拿到了业务类InterService的beanDefinition,我都拿到你的beanDefinition了,那么我不但可以get到你的信息,我也可以set你的信息从而改变你的行为来影响你后续的实例化。我们来编写另一个业务类:

public class User {
    private int age =31;
    private String name="myname";
}

spring启动时也会把这个业务类扫描,接下来,看好了,我在bean工厂中偷梁换柱,在beanDefinition中将你的InterService业务类替换掉:

/**
 * 扫描注册成功完成后,spring自动调用后置处理器MyBeanFactoryPostProcessor的postProcessBeanFactory方法
 */
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
        GenericBeanDefinition beanDefinition =
                (GenericBeanDefinition) beanFactory.getBeanDefinition("interService");
        System.out.println(beanDefinition.getBeanClassName());
        System.out.println("开始偷梁换柱");
        beanDefinition.setBeanClass(User.class);
    }
}

测试一下:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.refresh();
        System.out.println("更改后的业务类:"+context.getBeanDefinition("interService").getBeanClassName());
    }
}

打印结果:


image.png

       what the f...
       你以为,spring插件是怎么来的?你以为,为什么那么多框架都能集成到spring,就是利用spring的开放性这么扩展出来的,上文给你展示的只是冰山一角角,你不懂spring源码,你怎么扩展?当然,你会说我又不做框架开发插件开发,恩,我对你的CRUD表示尊重。
       既然我们已经偷梁换柱把InterService替换掉了,那么spring在后续实例化过程中就不会实例化InterService,看下面代码:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.refresh();
        System.out.println("更改后的业务类:"+context.getBeanDefinition("interService").getBeanClassName());
        //我们尝试获取InterService实例
        context.getBean(InterService.class);
    }
}

打印结果报错,因为InterService不存在spring当中了:


image.png

       继续,看后置处理器中的这行代码:

GenericBeanDefinition beanDefinition =
                (GenericBeanDefinition) beanFactory.getBeanDefinition("interService");

       getBeanDefinition方法返回的BeanDefinition类型,为什么强转成GenericBeanDefinition,起始BeanDefinition接口中并没有setBeanClass这个方法,GenericBeanDefinition是他的实现,提供更丰富的功能。不同的BeanDefinition实现具有不同的作用。
       下一篇我们详细讲一下不同BeanDefinition的作用,BeanDefinition学精通后你基本迈入了spring源码大门。

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

推荐阅读更多精彩内容