Spring Bean 生命周期 (實例結合源碼徹底講透)

前言

本篇文章主要是要介紹如何在Spring IoC 容器中 如何管理Spring Bean生命周期。

在應用開發中,常常需要執行一些特定的初始化工作,這些工作都是相對比較固定的,比如建立數據庫連接,打開網絡連接等,同時,在結束服務時,也有一些相對固定的銷毀工作需要執行。為了便于這些工作的設計,Spring IoC容器提供了相關的功能,可以讓應用定制Bean的初始化和銷毀過程。

Spring Bean 生命周期

圖片描述

先來看看 Spring Bean 的生命周期流程圖。結合圖看后面的描述會更輕松一點哦。

spring bean life cycle.png

文字描述

  1. Bean容器在配置文件中找到Spring Bean的定義。
  2. Bean容器使用Java Reflection API創建Bean的實例。
  3. 如果聲明了任何屬性,聲明的屬性會被設置。如果屬性本身是Bean,則將對其進行解析和設置。
  4. 如果Bean類實現BeanNameAware接口,則將通過傳遞Bean的名稱來調用setBeanName()方法。
  5. 如果Bean類實現BeanClassLoaderAware接口,則將通過傳遞加載此Bean的ClassLoader對象的實例來調用setBeanClassLoader()方法。
  6. 如果Bean類實現BeanFactoryAware接口,則將通過傳遞BeanFactory對象的實例來調用setBeanFactory()方法。
  7. 如果有任何與BeanFactory關聯的BeanPostProcessors對象已加載Bean,則將在設置Bean屬性之前調用postProcessBeforeInitialization()方法。
  8. 如果Bean類實現了InitializingBean接口,則在設置了配置文件中定義的所有Bean屬性后,將調用afterPropertiesSet()方法。
  9. 如果配置文件中的Bean定義包含init-method屬性,則該屬性的值將解析為Bean類中的方法名稱,并將調用該方法。
  10. 如果為Bean Factory對象附加了任何Bean 后置處理器,則將調用postProcessAfterInitialization()方法。
  11. 如果Bean類實現DisposableBean接口,則當Application不再需要Bean引用時,將調用destroy()方法。
  12. 如果配置文件中的Bean定義包含destroy-method屬性,那么將調用Bean類中的相應方法定義。

實例演示

接下來,我們用一個簡單的DEMO來演示一下,整個生命周期的流轉過程,加深你的印象。

  1. 定義一個Person類,實現了DisposableBean, InitializingBean, BeanFactoryAware, BeanNameAware這4個接口,同時還有自定義的init-methoddestroy-method。這里,如果不了解這幾個接口的讀者,可以先去看看這幾個接口的定義。

public class Person implements DisposableBean, InitializingBean, BeanFactoryAware, BeanNameAware {

    private String name;

    Person() {
        System.out.println("Constructor of person bean is invoked!");
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("setBeanFactory method of person is invoked");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("setBeanName method of person is invoked");
    }

    public void init() {
        System.out.println("custom init method of person bean is invoked!");
    }

    //Bean initialization code  equals to
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet method of person bean is invoked!");
    }

    //Bean destruction code
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean Destroy method of person bean is invoked!");
    }

    public void destroyMethod() {
        System.out.println("custom Destroy method of person bean is invoked!");
    }

}
  1. 定義一個MyBeanPostProcessor實現BeanPostProcessor接口。
public class MyBeanPostProcessor implements BeanPostProcessor {


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("post Process Before Initialization is invoked");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("post Process after Initialization is invoked");
        return bean;
    }
}

  1. 配置文件,指定init-methoddestroy-method屬性
<?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">

    <bean name="myBeanPostProcessor" class="ric.study.demo.ioc.life_cycle_demo_set.MyBeanPostProcessor" />
    <bean name="personBean" class="ric.study.demo.ioc.life_cycle_demo_set.Person"
          init-method="init" destroy-method="destroyMethod">
        <property name="name" value="Richard Yi" />
    </bean>

</beans>
  1. 啟動容器、銷毀容器
public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config-1.xml");
        ((ClassPathXmlApplicationContext) context).destroy();
    }
}
  1. 輸出
Constructor of person bean is invoked!
setBeanName method of person is invoked
setBeanFactory method of person is invoked
post Process Before Initialization is invoked
afterPropertiesSet method of person bean is invoked!
custom init method of person bean is invoked!
post Process after Initialization is invoked
DisposableBean Destroy method of person bean is invoked!
custom Destroy method of person bean is invoked!

可以看到這個結果和我們上面描述的一樣。

源碼解析

下面我們從源碼角度來看看,上述描述的調用是如何實現的。

實際上如果你看過我之前的文章 Spring IoC 依賴注入 源碼解析的話,應該知道上述調用的具體實現。

這里相當于把相關部分再拎出來講一遍。

容器初始化

Spring IoC 依賴注入的階段,創建Bean有三個關鍵步驟

  1. createBeanInstance() 實例化
  2. populateBean(); 屬性裝配
  3. initializeBean() 處理Bean初始化之后的各種回調事件

其中,initializeBean()負責處理Bean初始化后的各種回調事件。

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    invokeAwareMethods(beanName, bean);
                    return null;
                }
            }, getAccessControlContext());
        }
        else {
            // 涉及到的回調接口點進去一目了然,代碼都是自解釋的
            // BeanNameAware、BeanClassLoaderAware或BeanFactoryAware
            invokeAwareMethods(beanName, bean);
        }

        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
            // BeanPostProcessor 的 postProcessBeforeInitialization 回調
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
            // init-methods
            // 或者是實現了InitializingBean接口,會調用afterPropertiesSet() 方法
            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()) {
            // BeanPostProcessor 的 postProcessAfterInitialization 回調
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }

其中invokeAwareMethods會先調用一系列的***Aware接口實現

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

然后再執行 BeanPostProcessorpostProcessBeforeInitialization 回調

    @Override
    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
            throws BeansException {

        Object result = existingBean;
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
            result = beanProcessor.postProcessBeforeInitialization(result, beanName);
            if (result == null) {
                return result;
            }
        }
        return result;
    }

然后再調用 初始化方法,其中包括 InitializingBeanafterPropertiesSet方法和指定的init-method方法,

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
            throws Throwable {

        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
            }
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        @Override
                        public Object run() throws Exception {
                            ((InitializingBean) bean).afterPropertiesSet();
                            return null;
                        }
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }

        if (mbd != null) {
            String initMethodName = mbd.getInitMethodName();
            if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                    !mbd.isExternallyManagedInitMethod(initMethodName)) {
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }

最后再執行 BeanPostProcessorpostProcessAfterInitialization 回調

    @Override
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {

        Object result = existingBean;
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
            result = beanProcessor.postProcessAfterInitialization(result, beanName);
            if (result == null) {
                return result;
            }
        }
        return result;
    }

好的,到這里我們介紹了Spring 容器初始化過程Bean加載過程當中的各種回調實現,下面介紹Spring 容器銷毀階段。

容器關閉

與Bean初始化類似,當容器關閉時,可以看到對Bean銷毀方法的調用。銷毀過程是這樣的。順著close()-> doClose() -> destroyBeans() -> destroySingletons() -> destroySingleton() -> destroyBean() -> bean.destroy(),會看到最終調用Bean的銷毀方法。

protected void destroyBean(String beanName, DisposableBean bean) {
        // 忽略

        // Actually destroy the bean now...
        if (bean != null) {
            try {
                bean.destroy();
            }
            catch (Throwable ex) {
                logger.error("Destroy method on bean with name '" + beanName + "' threw an exception", ex);
            }
        }

        // 忽略
    }

這里注意哦,這個Bean的類型實際上是DisposableBeanAdapter,DisposableBeanAdapter是管理Spring Bean的銷毀的,實際上這里運用了適配器模式。再來看看destroy()的具體方法。

@Override
    public void destroy() {
        if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
            for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
                processor.postProcessBeforeDestruction(this.bean, this.beanName);
            }
        }

        if (this.invokeDisposableBean) {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'");
            }
            try {
                if (System.getSecurityManager() != null) {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        @Override
                        public Object run() throws Exception {
                            ((DisposableBean) bean).destroy();
                            return null;
                        }
                    }, acc);
                }
                else {
                    // 調用 DisposableBean 的 destroy()方法
                    ((DisposableBean) bean).destroy();
                }
            }
            catch (Throwable ex) {
                String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
                if (logger.isDebugEnabled()) {
                    logger.warn(msg, ex);
                }
                else {
                    logger.warn(msg + ": " + ex);
                }
            }
        }

        if (this.destroyMethod != null) {
            // 調用 設置的destroyMethod
            invokeCustomDestroyMethod(this.destroyMethod);
        }
        else if (this.destroyMethodName != null) {
            Method methodToCall = determineDestroyMethod();
            if (methodToCall != null) {
                invokeCustomDestroyMethod(methodToCall);
            }
        }
    }

BeanPostProcessor 是什么時候注冊到容器的?

前面只介紹了BeanPostProcessor類在 Spring Bean 生命周期中的回調實現,卻沒有說明 BeanPostProcessor 是什么時候注冊到容器的。下面我們來介紹下。

在Spring IoC 容器初始化的時候,容器會做一些初始化操作,其中就包括了BeanPostProcessor的register過程。詳細的過程可以看我這篇IoC 容器初始化

這里直接放源碼吧。

源碼位置AbstractApplicationContext#refresh()

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                // 在這里
                registerBeanPostProcessors(beanFactory);
            // ....忽略
        }
    }
    protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
    }

源碼位置PostProcessorRegistrationDelegate#registerBeanPostProcessors()

public static void registerBeanPostProcessors(
            ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
// step1
        // Register BeanPostProcessorChecker that logs an info message when
        // a bean is created during BeanPostProcessor instantiation, i.e. when
        // a bean is not eligible for getting processed by all BeanPostProcessors.
        int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
        beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

// step2
        // Separate between BeanPostProcessors that implement PriorityOrdered,
        // Ordered, and the rest.
        List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
        List<BeanPostProcessor> internalPostProcessors = new ArrayList<BeanPostProcessor>();
        List<String> orderedPostProcessorNames = new ArrayList<String>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
        for (String ppName : postProcessorNames) {
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
                priorityOrderedPostProcessors.add(pp);
                if (pp instanceof MergedBeanDefinitionPostProcessor) {
                    internalPostProcessors.add(pp);
                }
            }
            else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            }
            else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }
// step3
        // First, register the BeanPostProcessors that implement PriorityOrdered.
        sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
        registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

        // Next, register the BeanPostProcessors that implement Ordered.
        List<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>();
        for (String ppName : orderedPostProcessorNames) {
            BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
            orderedPostProcessors.add(pp);
            if (pp instanceof MergedBeanDefinitionPostProcessor) {
                internalPostProcessors.add(pp);
            }
        }
        sortPostProcessors(orderedPostProcessors, beanFactory);
        registerBeanPostProcessors(beanFactory, orderedPostProcessors);

        // Now, register all regular BeanPostProcessors.
        List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
        for (String ppName : nonOrderedPostProcessorNames) {
            BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
            nonOrderedPostProcessors.add(pp);
            if (pp instanceof MergedBeanDefinitionPostProcessor) {
                internalPostProcessors.add(pp);
            }
        }
        registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

        // Finally, re-register all internal BeanPostProcessors.
        sortPostProcessors(internalPostProcessors, beanFactory);
        registerBeanPostProcessors(beanFactory, internalPostProcessors);

        // Re-register post-processor for detecting inner beans as ApplicationListeners,
        // moving it to the end of the processor chain (for picking up proxies etc).
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    }

上述過程可以分成四步:

  1. 通過beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);方法獲取beanFactory里繼承了BeanPostProcessor接口的name的集合;
  2. 把后置器beans分為PriorityOrdered、Ordered、nonOrdered三大類,前兩類是增加了排序條件的后置器;(Spring可以通過PriorityOrderedOrdered接口控制處理器的優先級),這里實際上還有一類是MergedBeanDefinitionPostProcessor,不是核心點,不展開講。
  3. 第三步可以分為以下小步
    1. priorityOrderedPostProcessors,先排序后注冊
    2. orderedPostProcessors,先排序后注冊
    3. 注冊nonOrderedPostProcessors,就是一般的處理器
    4. internalPostProcessors,先排序后注冊
    5. 注冊一個ApplicationListenerDetector的 processor

DisposableBeanAdapter 什么時候注冊到容器的?

DisposableBeanAdapter和上文的BeanPostProcessor的抽象層級不同,這個是和Bean綁定的,所以它的注冊時機是在Spring Bean的依賴注入階段,詳細源碼可以看我的這篇文章Spring IoC 依賴注入 源碼解析

源碼位置:AbstractAutowireCapableBeanFactory#doCreateBean()

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
            throws BeanCreationException {
        // 省略前面的超多步驟,想了解的可以去看源碼或者我的那篇文章

        // Register bean as disposable.
        // 這里就是DisposableBeanAdapter的注冊步驟了
        try {
            registerDisposableBeanIfNecessary(beanName, bean, mbd);
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
        }

        return exposedObject;
    }

源碼位置:AbstractBeanFactory#registerDisposableBeanIfNecessary()

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()) {
                // 注冊一個DisposableBean實現,該實現將執行給定bean的所有銷毀工作。
                // 包括:DestructionAwareBeanPostProcessors,DisposableBean接口,自定義destroy方法。
                registerDisposableBean(beanName,
                        new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), 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, getBeanPostProcessors(), acc));
            }
        }
    }

結語

至此,Spring Bean的整個生命周期算是講解完了,從容器初始化到容器銷毀,以及回調事件的注冊時機等方面都說明了一下,希望能對你有所幫助。

本文由博客一文多發平臺 OpenWrite 發布!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容