(9)bean的生命周期

本文章主要围绕bean的生命周期来进行讲解,比如bean的实例化之后可以做什么,还有bean销毁之前该做什么

1. 生命周期回调

开发者通过实现Spring的InitializeingBeanDisposableBean接口,就可以让容器来管理Bean的生命周期。容器会调用afterPropertiesSet()前和destroy()后才会允许Bean在初始化和销毁Bean的时候执行一些操作。

JSR-250的@PostConstruct和@PreDestroy注解就是现代Spring应用生命周期回调的最佳实践。使用这些注解意味着Bean不在耦合在Spring特定的接口上。详细内容,后续将会介绍。
如果开发者不想使用JSR-250的注解,仍然可以考虑使用init-methoddestroy-method定义来解耦。

内部来说,Spring框架使用BeanPostProcessor的实现来处理任何接口的回调,BeanPostProcessor能够找到并调用合适的方法。如果开发者需要一些Spring并不直接提供的生命周期行为,开发者可以自行实现一个BeanPostProcessor。更多的信息可以参考后面的容器扩展点。

除了初始化和销毁回调,Spring管理的对象也实现了Lifecycle接口来让管理的对象在容器的生命周期内启动和关闭

接下来会按照下面的目录来讲

初始化回调(有三种方式)
  • 实现InitializingBean接口,然后重写afterPropertiesSet()方法
  • 使用@PostConstruct
  • 指定bean使用init-method属性,也可以用基于javabean的配置形式在@Bean之中指定initMethod属性
销毁回调(也有三种方式)
  • 实现DisposableBean接口,然后重写destroy()方法
  • 使用@PreDestroy
  • 指定bean使用destroy-method属性,也可以用基于javabean的配置形式在@Bean之中指定destroyMethod属性
联合生命周期机制
启动和关闭回调
在非Web应用关闭Spring IoC容器
  • 初始化回调
    具体的演示的bean如下:
/**
 * @Project: spring
 * @description:  初始化回调的方法:主要有三种方式实现
 * 调用过程如下被执行:
 * 1.有@PostConstruct注解的方法
 * 2.实现了InitializingBean接口重写了afterPropertiesSet的方法被执行
 * 3.init-method指定的方法被执行
 *
 * @author: sunkang
 * @create: 2018-09-15 18:10
 * @ModificationHistory who      when       What
 **/
public class InitializingBeanDemo implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("调用afterPropertiesSet初始化方法");
    }

    public void init(){
        System.out.println("调用init初始化方法");
    }

    @PostConstruct
    public void postConstrct(){
        System.out.println("调用postConstrct初始化方法");
    }

    public InitializingBeanDemo() {
        System.out.println("初始化InitializingBeanDemo对象");
    }
}

在spring-lifecycle.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启注解-->
    <context:annotation-config/>
    <bean id="initializing" class="com.spring.lifecycle.InitializingBeanDemo" init-method="init"/>
</beans>

具体的测试案例

/**
 * @Project: spring
 * @description:  测试
 * @author: sunkang
 * @create: 2018-09-15 18:14
 * @ModificationHistory who      when       What
 **/
public class LifeCycleTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("lifecycle/spring-lifecycle.xml");
}

具体的测试结果信息如下: 可以发现现实初始化之后,才进行初始化方法的回调,回调的顺序是postConstrct--》afterPropertiesSet---》init-method

初始化InitializingBeanDemo对象
调用postConstrct初始化方法
调用afterPropertiesSet初始化方法
调用init初始化方法

总结:Spring团队建议开发者不要使用InitializingBean接口,因为这样会将不必要的代码耦合到Spring之上。而通过使用@PostConstruct注解或者指定一个POJO的实现方法,比实现接口要更好。在基于XML的配置元数据上,开发者可以使用init-method属性来指定一个没有参数的方法

  • 销毁回调
    具体的演示如下:
/**
 * @Project: spring
 * @description:       销毁回调  主要也有三种方式
 * 执行的调用顺序如下:
 * 1. @PreDestroy注解所指明的preDestroy方法
 * 2. DisposableBean的destroy的方法
 * 3. destroy-method指定的destroyMethod方法
 * @author: sunkang
 * @create: 2018-09-15 18:19
 * @ModificationHistory who      when       What
 **/
public class DisposableBeanDemo implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("调用DisposableBean的destroy的销毁方法");
    }
    public void  destroyMehtod(){
        System.out.println("调用destroyMehtod的销毁方法");
    }
    @PreDestroy
    public void  preDestroy(){
        System.out.println("调用preDestroy的销毁方法");
    }
    public DisposableBeanDemo() {
        System.out.println("初始化DisposableBeanDemo");
    }
}

在spring-lifecycle.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解-->
    <context:annotation-config/>

    <bean  id = "destroy" class="com.spring.lifecycle.DisposableBeanDemo"  destroy-method="destroyMehtod"/>

测试方法,如何模拟容器的关闭呢,强迫容器在关闭之前执行销毁的回调方法,可以用容器的close方法来模拟容器的关闭

/**
 * @Project: spring
 * @description:  测试
 * @author: sunkang
 * @create: 2018-09-15 18:14
 * @ModificationHistory who      when       What
 **/
public class LifeCycleTest {
    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("lifecycle/spring-lifecycle.xml");
        context.stop();

        context.close();
    }
}

测试结果:

初始化DisposableBeanDemo
调用preDestroy的销毁方法
调用DisposableBean的destroy的销毁方法
调用destroyMehtod的销毁方法

总结:
同InitializingBean同样,Spring团队仍然不建议开发者来使用DisposableBean回调接口,因为这样会将开发者的代码耦合到Spring代码上。换种方式,比如使用@PreDestroy注解或者指定一个Bean支持的配置方法,比如在基于XML的配置元数据中,开发者可以在Bean标签上指定destroy-method属性。而在Java配置中,开发者可以配置@Bean的destroyMethod。

  • 联合生命周期机制

通过初始化回调和销毁回调的演示可以看出:

如果一个Bean配置了多个生命周期机制,并且含有不同的方法名,执行的顺序如下

  • 包含@PostConstruct注解的方法
  • InitializingBean接口中的afterPropertiesSet()方法
  • 自定义的init()方法

销毁方法的执行顺序和初始化的执行顺序相同:

  • 包含@PreDestroy注解的方法
  • 在DisposableBean接口中的destroy()方法
  • 自定义的destroy()方法
  • 启动和关闭回调
    Lifecycle接口中为任何有自己生命周期需求的对象定义了基本的方法(比如启动和停止一些后台进程):
public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();

}

任何Spring管理的对象都可实现上面的接口。那么当ApplicationContext本身受到了启动或者停止的信号时,ApplicationContext会通过委托LifecycleProcessor来串联上下文中的Lifecycle的实现。

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

从上面代码我们可以发现LifecycleProcessor是Lifecycle接口的扩展。LifecycleProcessor增加了另外的两个方法来针对上下文的刷新关闭做出反应

启动和关闭调用是很重要的。如果不同的Bean之间存在depends-on的关系的话,被依赖的一方需要更早的启动,而且关闭的更早。然而,有的时候直接的依赖是未知的,而开发者仅仅知道哪一种类型需要更早进行初始化。在这种情况下,SmartLifecycle接口定义了另一种选项,就是其父接口Phased中的getPhase()方法。

public interface Phased {

    int getPhase();

}
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);

}

当启动时,拥有最低的phased的对象优先启动而当关闭时,是相反的顺序。因此,如果一个对象实现了SmartLifecycle然后令其getPhase()方法返回了Integer.MIN_VALUE的话,就会让该对象最早启动,而最晚销毁。显然,如果getPhase()方法返回了Integer.MAX_VALUE就说明了该对象会最晚启动,而最早销毁。当考虑到使用phased的值得时候,也同时需要了解正常没有实现SmartLifecycle的Lifecycle对象的默认值,这个值为0。因此,任何负值将标准对象会在标准组件启动之前启动,在标准组件销毁以后再进行销毁
现在来模拟演示SmartLifecycle的启动的过程

package com.spring.lifecycle;

import org.springframework.context.Lifecycle;
import org.springframework.context.LifecycleProcessor;
import org.springframework.context.SmartLifecycle;

/**
 * @Project: spring
 * @description:   SmartLifecycleDemo 
 * @author: sunkang
 * @create: 2018-09-15 19:05
 * @ModificationHistory who      when       What
 **/
public class SmartLifecycleDemo implements SmartLifecycle {

    private boolean isRunning = false;
    /**
     * 1. 我们主要在该方法中启动任务或者其他异步服务,比如开启MQ接收消息<br/>
     * 2. 当上下文被刷新(所有对象已被实例化和初始化之后)时,将调用该方法,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。
     * 如果为“true”,则该方法会被调用,而不是等待显式调用自己的start()方法。
     */
    @Override
    public void start() {
        System.out.println("start");
        // 执行完其他业务后,可以修改 isRunning = true
        isRunning = true;
    }

    /**
     * 如果工程中有多个实现接口SmartLifecycle的类,则这些类的start的执行顺序按getPhase方法返回值从小到大执行。<br/>
     * 例如:1比2先执行,-1比0先执行。 stop方法的执行顺序则相反,getPhase返回值较大类的stop方法先被调用,小的后被调用。
     */
    @Override
    public int getPhase() {
        System.out.println("getPhase");
        // 默认为0
        return 0;
    }

    /**
     * 根据该方法的返回值决定是否执行start方法。<br/>
     * 返回true时start方法会被自动执行,返回false则不会。
     */
    @Override
    public boolean isAutoStartup() {
        System.out.println("isAutoStartup");
        // 默认为false
        return true;
    }

    /**
     * 1. 只有该方法返回false时,start方法才会被执行。<br/>
     * 2. 只有该方法返回true时,stop(Runnable callback)或stop()方法才会被执行。
     */
    @Override
    public boolean isRunning() {
        System.out.println("isRunning");
        // 默认返回false
        return isRunning;
    }

    /**
     * SmartLifecycle子类的才有的方法,当isRunning方法返回true时,该方法才会被调用。
     */
    @Override
    public void stop(Runnable callback) {
        System.out.println("stop(Runnable)");

        // 如果你让isRunning返回true,需要执行stop这个方法,那么就不要忘记调用callback.run()。
        // 否则在你程序退出时,Spring的DefaultLifecycleProcessor会认为你这个TestSmartLifecycle没有stop完成,程序会一直卡着结束不了,等待一定时间(默认超时时间30秒)后才会自动结束。
        // PS:如果你想修改这个默认超时时间,可以按下面思路做,当然下面代码是springmvc配置文件形式的参考,在SpringBoot中自然不是配置xml来完成,这里只是提供一种思路。
        // <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
        //      <!-- timeout value in milliseconds -->
        //      <property name="timeoutPerShutdownPhase" value="10000"/>
        // </bean>
        callback.run();

        isRunning = false;
    }

    /**
     * 接口Lifecycle的子类的方法,只有非SmartLifecycle的子类才会执行该方法。<br/>
     * 1. 该方法只对直接实现接口Lifecycle的类才起作用,对实现SmartLifecycle接口的类无效。<br/>
     * 2. 方法stop()和方法stop(Runnable callback)的区别只在于,后者是SmartLifecycle子类的专属。
     */
    @Override
    public void stop() {
        System.out.println("stop");
        isRunning = false;
    }

}

在spring-smartlifecycle.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <bean  id="lifeStyleDemo"  class="com.spring.lifecycle.SmartLifecycleDemo" />
    
</beans>

测试类:来模拟容器的启动,停止和关闭

/**
 * @Project: spring
 * @description:SmartLifeCycle 的测试
 * @author: sunkang
 * @create: 2018-09-17 14:33
 * @ModificationHistory who      when       What
 **/
public class SmartLifeCycleTest  {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        context.setConfigLocation("lifecycle/spring-smartlifecycle.xml");

        context.refresh();

        context.stop();

        context.close();

    }
}

看下测试结果:
可以发现在容器加载xml的配置和容器调用stop以及close的时候getPhase方法被调用了
在加载xml的时候,会先去判断是否是isRunning,如果是,则调用isAutoStartup判断是否是自动启动的,如果是则调用start方法
调用stop方法之后也是需要判断isRunning的状态然后调用stop(Runnable)方法,最后是关闭

九月 17, 2018 2:41:11 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@23ab930d: startup date [Mon Sep 17 14:41:11 CST 2018]; root of context hierarchy
九月 17, 2018 2:41:11 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [lifecycle/spring-smartlifecycle.xml]
isAutoStartup
getPhase
isRunning
isAutoStartup
start
九月 17, 2018 2:41:11 下午 org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup start
信息: Starting beans in phase 0
九月 17, 2018 2:41:11 下午 org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup stop
信息: Stopping beans in phase 0
getPhase
isRunning
stop(Runnable)
九月 17, 2018 2:41:11 下午 org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@23ab930d: startup date [Mon Sep 17 14:41:11 CST 2018]; root of context hierarchy
getPhase
isRunning
九月 17, 2018 2:41:11 下午 org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup stop
信息: Stopping beans in phase 0
  • 在非Web应用关闭Spring IoC容器

如果开发者在非web应用环境使用Spring IoC容器的话, 比如,在桌面客户端的环境下,开发者需要在JVM上注册一个关闭的钩子。来确保在关闭Spring IoC容器的时候能够调用相关的销毁方法来释放掉对应的资源。当然,开发者也必须要正确的配置和实现那些销毁回调

public final class Boot {

    public static void main(final String[] args) throws Exception {

        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
                new String []{"beans.xml"});

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...

    }
}

我们可以看一下registerShutdownHook,里面的源码:发现这里面其实注册了一个钩子,当jvm离开的时候会调用system.exit方法,会调用Shutdown.exit(status),然后调用 sequence()方法,然后调用 runHooks(),这里面注册的 钩子将会在jvm关闭的时候执行,也就是说jvm离开的时候会调用 AbstractApplicationContext.this.doClose()方法,也就是销毁容器的方法

 public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            this.shutdownHook = new Thread() {
                public void run() {
                    synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
                        AbstractApplicationContext.this.doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }

    }

2.ApplicationContextAware和BeanNameAware

ApplicationContext在创建实现了org.springframework.context.ApplicationContextAware接口的对象时,该对象的实例会包含一个到ApplicationContext的引用。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

这样Bean就能够通过编程的方式操作和创建ApplicationContext了通过ApplicationContext接口,或者通过将引用转换成已知的接口的子类,比如ConfigurableApplicationContext就能够显式一些额外的功能其中的一个用法就是可以通过编程的方式来获取其他的Bean。有的时候这个能力很有用,然而,Spring团队推荐最好避免这样做,因为这样会耦合代码到Spring上,同时也没有遵循IoC的风格

当ApplicationContext创建了一个实现了org.springframework.beans.factory.BeanNameAware接口的类,那么这个类就可以针对其名字进行配置。

public interface BeanNameAware {

    void setBeanName(string name) throws BeansException;

}

这个回调的调用处于属性配置完以后,但是初始化回调之前(比如·InitializingBean的afterPropertiesSet()方法以及自定义的初始化方法·等)

下面进行简单的演示:

/**
 * @Project: spring
 * @description: ApplicationContextAware 和BeanNameAware的演示
 * @author: sunkang
 * @create: 2018-09-15 19:21
 * @ModificationHistory who      when       What
 **/
public class ApplicationContextAwareDemo implements ApplicationContextAware,BeanNameAware {
    public void systemName(){
        System.out.println("我是ApplicationContextAwareDemo");
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        applicationContext.getBean("com.spring.lifecycle.ApplicationContextAwareDemo#0",ApplicationContextAwareDemo.class).systemName();
    }
    @Override
    public void setBeanName(String s) {
        System.out.println(s);
    }

}

在xml的配置如下:

    <bean   class="com.spring.lifecycle.ApplicationContextAwareDemo"/>

测试结果如下: 可以发现如果在容易中没有配置name或者id属性,那么spring容器会自动生成一个唯一的id,命令规则是类名称的全限定名+“#”+序号,实现了BeanNameAware 可以获取bean的名称,实现了ApplicationContextAware可以获取applicationContext容器对象

com.spring.lifecycle.ApplicationContextAwareDemo#0
我是ApplicationContextAwareDemo

3.其他Aware接口

除了上面描述的两种Aware接口,Spring还提供了一系列的Aware接口来让Bean告诉容器,这些Bean需要一些具体的基础设施信息。最重要的一些Aware接口都在下面表中进行了描述:

名字 注入的依赖
ApplicationContextAware 声明的ApplicationContext
ApplicationEventPlulisherAware ApplicationContext中的事件发布器
BeanClassLoaderAware 加载Bean使用的类加载器
BeanFactoryAware 声明的BeanFactory
BeanNameAware Bean的名字
BootstrapContextAware 容器运行的资源适配器BootstrapContext,通常仅在JCA环境下有效
LoadTimeWeaverAware 加载期间处理类定义的weaver
MessageSourceAware 解析消息的配置策略
NotificationPublisherAware Spring JMX通知发布器
PortletConfigAware 容器当前运行的PortletConfig,仅在web下的Spring ApplicationContext中可见
PortletContextAware 容器当前运行的PortletContext,仅在web下的Spring ApplicationContext中可见
ResourceLoaderAware 配置的资源加载器
ServletConfigAware 容器当前运行的ServletConfig,仅在web下的Spring ApplicationContext中可见
ServletContextAware 容器当前运行的ServletContext,仅在web下的Spring ApplicationContext中可见

再次的声明,上面这些接口的使用时违反IoC原则的,除非必要,最好不要使用。

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

推荐阅读更多精彩内容