【啃啊啃 Spring5 源码】细碎四:核心类总结

[TOC]

阅读spring源码时,有许多 “核心类” 的作用我们了解了,才会阅读的更顺畅。这里总结下我阅读源码时认为比较重要,需要了解的一些 “核心类”

注:本文前三节 为参考【Spring4揭秘 基础2】PropertySource和Enviroment系列文章,进行的总结扩展

1. 资源配置相关

1.1 Resource

public interface Resource extends InputStreamSource {  
       URL getURL() throws IOException;  
       URI getURI() throws IOException;  
       File getFile() throws IOException;  
       
       …………
}  

Resource 主要是对spring中各种资源(包括文件系统资源、class路径资源、Byte、servletContext资源、url资源)的抽象,spring中xml配置文件便是通过Resource 子类读取。

它继承了InputStreamSource接口,子类通过重写getInputStream()方法,便可轻松的读取各种资源。解决了Java中不同资源读取需求写不同代码的困难。

常见子类

  • ClassPathResource:对class文件的读取,默认路径为项目根路径(/target),也可指定Class或ClassLoader来获取不同路径。
  • FileSystemResource:对文件系统资源的读和写,路径为文件系统路径,例如windos中:“D://a.json”
  • ByteArrayResource:对byte[]数组资源的读取
  • UrlResource:对url网络资源的读取
  • ServletContextResource:对web工程中文件的读取。通过ServletContext读取webRoot目录下的资源
  • InputStreamResource:对输入流资源的读取,直接通过给定的输入流读取。
  • BeanDefinitionResource:对BeanDefinition的读取,主要通过getBeanDefinition()方法,而不是getInputStream()

1.2 ResourceLoader

public interface ResourceLoader {
     /**
      * 根据给定的路径,返回不同的Resource。例如路径中包含“classpath:”,则返回ClassPathResource
       */
    Resource getResource(String location);
}

从源码也可以看出,ResourceLoader其实就是Resource的工厂类。根据资源位置 “location” 的不同,返回不同的Resource供使用者读取资源。

常见子类

  • DefaultResourceLoaderResourceLoader的基本功能实现类。返回Resource的策略为:location中包含“classpath:”,则返回ClassPathResource;包含“/”或出现异常返回ClassPathContextResource;其他情况返回UrlResource
  • FileSystemResourceLoader:继承DefaultResourceLoader,重写getResourceByPath()方法,返回文件资源加载类FileSystemContextResource
  • ServletContextResourceLoader:继承DefaultResourceLoader,重写getResourceByPath()方法,返回servletContext资源加载类ServletContextResource
    image.png

1.3 ResourcePatternResolver

public interface ResourcePatternResolver extends ResourceLoader {

    /**
     * 多resource匹配location 前缀
     */
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    /**
     * 根据给定路径返回多个资源
     */
    Resource[] getResources(String locationPattern) throws IOException;
}

ResourcePatternResolver继承ResourceLoader接口,增加了getResources()方法用于根据匹配多个资源返回。例如对于路径:“classpath:.xml” 将匹配class路径下的所有的xml文件,加载成对应Resource后返回。

常见子类

  • PathMatchingResourcePatternResolver:接口的基础功能实现类,从类名也可看出,可解析通配符路径(如“application*.xml”)来返回Resources
  • ServletContextResourcePatternResolver:解析webRoot目录下的通配符路径,返回Resources
  • ApplicationContext:spring中的容器接口也继承了ResourcePatternResolver接口,代表spring中的所有容器都有根据通配符路径获取资源的功能

2. 环境属性相关

2.1 PropertySource

public abstract class PropertySource<T> {
    protected final String name;//属性源名称
    protected final T source;//属性源(包含键值对的对象,如Properties、Map、ServletContext等)
    public String getName();  //获取属性源的名字  
    public T getSource();        //获取属性源  
    public boolean containsProperty(String name);  //是否包含某个属性  
    public abstract Object getProperty(String name);   //得到属性名对应的属性值   
} 

PropertySource 代表包含若干键值对(key-value)的数据源(source)。这个源可以是PropertiesMapServletContext等。在spring中,读取的propertie配置、servlet中的环境参数、乃至JDK系统参数、系统变量,都会以PropertySource的形式存在。

常见子类

  • MapPropertySource:source为一个Map,也就是键值对存在Map中
  • PropertiesPropertySource:从名字也可以看出,该类代表properties文件中键值对。继承MapPropertySource,source为Properties对象(Properties继承了HashMap,所以PropertiesPropertySource也是一个MapPropertySource)。
  • SystemEnvironmentPropertySource:继承MapPropertySource,source为Map对象,代表系统中的环境变量,如JAVA_HOME、MABEN_HOME之类。 与MapPropertySource不同的是,取值时它将会忽略大小写,”.”和”_”将会转化
  • ResourcePropertySource:source为Resource,代表spring中resource资源(例如:“/com/myco/foo.properties”、“file:/path/to/file.xml”)中的键值对属性
  • ServletContextPropertySource:source为ServletContext,代表web环境中的servlet上下文中的参数

2.2 PropertySources

public interface PropertySources extends Iterable<PropertySource<?>> {

    /**
     * 返回是否包含该名称的PropertySource
     */
    boolean contains(String name);

    /**
     *返回指定名称的PropertySource
     */
    @Nullable
    PropertySource<?> get(String name);

}

PropertySources即多个PropertySource,可看成PropertySource集合。

常见子类

  • MutablePropertySources:PropertySources接口的默认也是唯一实现类,内部维护了一个PropertySource的集合(CopyOnWriteArrayList实现)。支持向集合的头部、末尾、及指定位置添加PropertySource

2.3 PropertyResolver

public interface PropertyResolver {  

    //是否包含某个属性  
    boolean containsProperty(String key);  

    //获取属性值 如果找不到返回null   
    String getProperty(String key);  

    //获取属性值,如果找不到返回默认值        
    String getProperty(String key, String defaultValue);  

    //获取指定类型的属性值,找不到返回null  
    <T> T getProperty(String key, Class<T> targetType);  

    //获取指定类型的属性值,找不到返回默认值  
    <T> T getProperty(String key, Class<T> targetType, T defaultValue);  

    //获取属性值为某个Class类型,找不到返回null,如果类型不兼容将抛出ConversionException  
    <T> Class<T> getPropertyAsClass(String key, Class<T> targetType);  

    //获取属性值,找不到抛出异常IllegalStateException  
    String getRequiredProperty(String key) throws IllegalStateException;  

    //获取指定类型的属性值,找不到抛出异常IllegalStateException         
    <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;  

    //替换文本中的占位符(${key})到属性值,找不到不解析  
    String resolvePlaceholders(String text);  

    //替换文本中的占位符(${key})到属性值,找不到抛出异常IllegalArgumentException  
    String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;  
}  

实现PropertyResolver接口的子类,一般拥有了PropertySources的成员变量,该接口主要用于从PropertySources中获取对应属性。

从接口的resolvePlaceholders()方法可以看出,该接口还有解析文本,将文本中占位符转换为PropertySources对应属性的功能。spring配置文件中的占位符最后便是这样被替换成对应的属性。

2.4 Environment

public interface Environment extends PropertyResolver {  
        //得到当前激活的配置环境 
    String[] getActiveProfiles();  

        //得到默认激活的配置环境
    String[] getDefaultProfiles();  

        //是否接受某些配置环境
    boolean acceptsProfiles(String... profiles);  
}  

Environment接口继承了PropertyResolver接口,主要添加了“激活配置环境”功能。

在实际开发中,一般维护了多套开发环境,例如:dev、test、uat、prodcut。每种开发环境的配置是有差别的,spring中实现了多环境配置的功能:

如下,定义了test、dev两个配置环境,它们加载的bean并不相同,启动spring时,可指定active profile来加载指定配置环境下的bean

    <beans profile="test">
        <bean class="test.wsz.spring.aop.AspectDemo" />
        <bean id="a" class="test.wsz.spring.bean.A" >
            <constructor-arg value="wsz1" index="0" />
        </bean>
    </beans>

    <beans profile="dev">
        <bean id="b" class="test.wsz.spring.bean.B" />
        <bean class="test.wsz.spring.postProcess.MyBeanPostProcessor" />
    </beans>

常见子类

  • AbstractEnvironmentEnvironment的具体功能实现类
  • StandardEnvironment:标准环境类,会自动注册System.getProperties()中的属性到环境中。spring应用启动时采用该环境类。
  • StandardServletEnvironment:标准servlet环境类,web应用时采用。

3. bean配置相关

spring中bean对象的生成可以分成两步:

  1. 读取配置文件中bean的配置,将其转换为bean配置类:BeanDefinition(持有beanName、beanClass、properties等信息)
  2. 根据BeanDefinition中的配置信息,实例化并初始化bean

本节主要介绍第一步,bean配置相关的类。

3.1 BeanDefinition

ublic interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

    //单例或原型
    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; 
    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

     //Bean角色
    int ROLE_APPLICATION = 0; 
    int ROLE_SUPPORT = 1; 
    int ROLE_INFRASTRUCTURE = 2;

    // 返回/设置父BeanDefinition 
    String getParentName(); 
    void setParentName(String parentName);

    //返回/设置 当前的BeanClassName(不等于最终Bean的名称)
    String getBeanClassName(); 
    void setBeanClassName(String beanClassName);

    //返回设置 factory bean name  
    String getFactoryBeanName(); 
    void setFactoryBeanName(String factoryBeanName);

    String getFactoryMethodName(); 
    void setFactoryMethodName(String factoryMethodName);
    
    //返回/设置 bean的Scope:单例、原型等等
    String getScope(); 
    void setScope(String scope);

    //返回/设置 bean是否懒加载
    boolean isLazyInit(); 
    void setLazyInit(boolean lazyInit);

    String[] getDependsOn(); 
    void setDependsOn(String... dependsOn);

    boolean isAutowireCandidate(); 
    void setAutowireCandidate(boolean autowireCandidate);

    boolean isPrimary(); 
    void setPrimary(boolean primary);

    ConstructorArgumentValues getConstructorArgumentValues();
    
    //返回/设置 bean的属性
    MutablePropertyValues getPropertyValues(); 
    boolean isSingleton(); 
    boolean isPrototype(); 
    boolean isAbstract(); 
    int getRole(); 
    String getDescription(); 
    String getResourceDescription(); 
    BeanDefinition getOriginatingBeanDefinition();
}

从接口方法可以看出,BeanDefiniton主要包含了bean的名称、class、scope、属性、描述、懒加载等配置信息。

常见子类

  • GenericBeanDefinition:一个综合性的标准BeanDefiniton,包含了BeanDefinition中的主要功能
  • AnnotatedGenericBeanDefinition:一个注解式的标准BeanDefiniton,继承了GenericBeanDefinition,在其基础上增加了对类注解配置的支持,即可通过该类获取注解配置。
  • ConfigurationClassBeanDefinition:如果spring中的配置是用类来表示的,则解析后生成的bean配置类为该类
image.png

3.2 BeanDefinitionHolder

public class BeanDefinitionHolder implements BeanMetadataElement {

    private final BeanDefinition beanDefinition;

    private final String beanName;

    @Nullable
    private final String[] aliases;

    public boolean matchesName(@Nullable String candidateName) {
        ……省略
    }
}

从类代码中可以看出,BeanDefinitionHolder其实就是一个持有了beanDefinition和bean名称beanName,及bean的别名数组aliases的类。

matchesName()方法可以知道,该类能够在beanDefinition注册时,匹配占位符,如果匹配中则注册该BeanDefinition

3.3 BeanDefinitionRegistry

public interface BeanDefinitionRegistry extends AliasRegistry {

     // 注册一个BeanDeinition
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException;

     // 删除注册的BeanDeinition
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    //获取注册的BeanDefinition
    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    boolean containsBeanDefinition(String beanName);

    String[] getBeanDefinitionNames();

    int getBeanDefinitionCount();

    boolean isBeanNameInUse(String beanName);

}

BeanDefinitionRegistry接口定义了‘注册/获取BeanDefinition’的方法。实现该接口的子类一般都维护了一个BeanDeinition集合:Map<String, BeanDefinition> beanDefinitionMap

常见子类

  • DefaultListableBeanFactory:这个类是spring容器功能的核心类,它实现了BeanDefinitionRegistry接口,代表spring中的beanFactory基本都有注册、删除BeanDefinition的功能
  • GenericApplicationContext:从类名也可以看出,该类是spring上下文的主要功能实现类,该类对BeanDefinition的注册、删除,实际是委托DefaultListableBeanFactory来实现
  • SimpleBeanDefinitionRegistryBeanDefinitionRegistry接口功能的简单实现,主要用来测试功能的……

3.4 BeanDefinitionReader

public interface BeanDefinitionReader {
    //获取BeanDefinition的注册器
    BeanDefinitionRegistry getRegistry();

    //获取用于加载配置的ResourceLoader(上文有提到)
    ResourceLoader getResourceLoader();

    //获取用于加载bean class的ClassLoader
    ClassLoader getBeanClassLoader();

    BeanNameGenerator getBeanNameGenerator();

        //加载解析配置,并注册解析后的BeanDefinition
    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

        //加载解析多个配置,并注册解析后的BeanDefinition
    int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;

    //根据配置路径,加载解析配置,并注册解析后的BeanDefinition
    int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;

    //根据多个配置路径,加载解析多个配置,并注册解析后的BeanDefinition
    int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;

}

BeanDefinitionReader接口的方法可以看出,这个接口主要实现了 直接从配置中加载并注册所有BeanDefinition的功能,注册的动作实际委托了BeanDefinitionRegister实现。

常见子类

  • AbstractBeanDefinitionReaderBeanDefinitionReader接口的主要功能实现抽象类
  • PropertiesBeanDefinitionReader:用于从properties文件中加载注册BeanDefinition(ps:我也是第一次知道,properties文件也可以配置bean,配置方式可见该类注释)
  • XmlBeanDefinitionReader:用于从xml文件中加载注册BeanDefinition

扩展
上面的BeanDefinitionReader子类都是从配置文件中加载注册beanDefinition,但是实际应用中,我们经常采用注解方式来声明一个bean。那这中注解bean又是如何被发现并注册的呢?

注解式bean的加载注册,主要看两个类:

  • AnnotatedBeanDefinitionReader:该类并没有实现BeanDefinitonReader接口,主要根据bean class来加载注册BeanDefinition
  • ClassPathBeanDefinitionScanner:从类名也可以看出,该类主要是扫描classpath中的注解类(@Component、@Repository、@Service、@Controller),然后加载注册对应的BeanDefinition

4. bean相关

4.1 BeanWrapper

public interface BeanWrapper extends ConfigurablePropertyAccessor {

    void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);

    int getAutoGrowCollectionLimit();

    //返回被包装的bean实例
    Object getWrappedInstance();

    //返回被包装的bean的class
    Class<?> getWrappedClass();

    //返回被包装的bean的属性集合
    PropertyDescriptor[] getPropertyDescriptors();

    //根据属性名称,返回被包装的bean的属性
    PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;

}

BeanWrapper是spring中bean的包装类的接口,从接口定义的方法可以看出,beanWrapper的子类包含bean的实例及属性信息。

它的父接口ConfigurablePropertyAccessor定义了setConversionService()方法来设置属性转换器(spring中ConversionService负责属性的转换,例如将 字符串“2018-08-20”转换为Date类型)。

ConfigurablePropertyAccessor继承的TypeConverter接口提供了属性转换的方法convertIfNecessary()。故BeanWrapper还有将bean中的属性进行转换的功能。

spring中实例化bean结束后,会将生成的BeanWrapper作为形参传给populateBean()方法,进行bean的初始化:即bean的属性设值。这个设值操作便会用到BeanWrapper的属性转换功能。

常见子类

  • BeanWrapperImplBeanWrapper接口的默认实现。继承了AbstractNestablePropertyAccessor类,父类中的registerCustomEditor()方法可增加自定义的PropertyEditor属性编辑器(PropertyEditor和ConversionService一样用于属性的转换,具体可看:SpringMVC数据类型转换

4.2 ObjectFactory

public interface ObjectFactory<T> {

    //返回一个被工厂管理的实例
    T getObject() throws BeansException;

}

就是一个很简单工厂接口,在调用时返回实例。在【啃啊啃 Spring5 源码】细碎二:bean的循环依赖中我们看到过它的运用:

为了解决bean的循环依赖,bean在第一次获取时,生成的ObjectFactory对象,getObject()方法实际上会调用createBean()方法创建bean。而bean在创建的过程中,实例化完成后会重新创建ObjectFactory对象,getObject()方法会变成调用getEarlyBeanReference(),用于获取的缓存的实例化的bean。这样依赖bean再通过ObjectFactory工厂获取实例时,获取的便是缓存中的bean。

4.3 FactoryBean

public interface FactoryBean<T> {

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }

}

FactoryBean对应spring中的工厂类bean。即定义的bean实际是一个工厂,可获取bean。这里不引申了,有兴趣的同学可以自行百度了解。

最后比较下ObjectFactoryFactoryBeanBeanFactory

  1. ObjectFactoryFactoryBean很类似,都是工厂类,用于获取bean。区别在于ObjectFactory是在代码中根据场景手动调用getObject()方法获取bean,而FactoryBean是配置写好,spring会自动调用getObject()方法获取bean。
  2. BeanFactory是容器的父类,管理着bean,并不能简单的看成工厂类,和前两者区别很大

5. 容器相关

5.1 BeanFactory

public interface BeanFactory {

    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;

    <T> T getBean(Class<T> requiredType) throws BeansException;

    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    boolean containsBean(String name);

    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    
    String[] getAliases(String name);

}

BeanFactory是spring容器的核心接口,spring中的容器类,都实现了该接口。定义了获取bean、获取bean类型、判断bean是否单例等基础方法。

常见子类

BeanFactory下面有三个子接口:

  • HierarchicalBeanFactory:提供容器分层功能,通过getParentBeanFactory()方法获取父容器
  • ListableBeanFactory:和类名一样,提供了getBeanDefinitionNames()列出容器内所有bean的名字、根据 “type” 获取所有bean名字 等方法
  • AutowireCapableBeanFactory:提供了自动装配的功能,根据类定义BeanDefinition装配Bean、执行前、后处理器等。

主要接口:

  • ConfigurableBeanFactory:该接口包含大量容器工厂的配置方法,从bean的注册、加载、销毁、依赖,到后处理器的添加,以及属性编辑器和类型转换……
  • ConfigurableListableBeanFactoryConfigurableBeanFactory接口的补充,另外还继承了其余的beanFactory接口,基本包含了BeanFactory体系目前的所有方法,可以看成BeanFactory体系中功能最完善的接口
  • ApplicationContext:spring容器的核心接口,继承了BeanFactory体系的接口,在其功能上增加了资源加载、事件体制、环境配置、国际化支持、bean生命周期管理等功能

核心类:

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