Spring解析之IoC:bean的加載(一)

前言
看過Spring解析前兩篇文章的讀者知道领迈,之前分析的思路一直都源自ClassPathXmlApplicationContext的初始化,但該類的初始化還未分析完畢為什么突然切成bean的加載呢暇昂?實際上在ClassPathXmlApplicationContext初始化剩下的部分中多次進行了bean的加載和獲取,當(dāng)然該流程是可以放在初始化過程中一并分析的,但bean加載的流程比較復(fù)雜,如果將該流程和初始化過程放在一起無疑加重了原本就難于理解的邏輯,此外惰匙,從容器中獲取對象是Spring最常用的功能技掏,單獨的剝離分析更加有利于整體脈絡(luò)的構(gòu)建,因此另開一文單獨講解项鬼。bean的加載由兩到三篇文章構(gòu)成哑梳,第一篇也就是本文,以顯式調(diào)用getBean為切入點

在首篇開始的例子中可知绘盟,通常我們手動獲取bean使用getBean方法鸠真,該方法有幾種重載形式悯仙,最普遍的為Obejct getBean(String),根據(jù)beanName得到實際類型為beanName對應(yīng)bean實例的Object對象吠卷;另一種是我喜歡用的T getBean(Class<T>)赏寇,這種方式省去了強轉(zhuǎn)過程营搅。不管任何一種獲取bean的方式都殊途同歸,我們先以Obejct getBean(String)為模板進行分析

圖1. AbstractApplicationContext中獲取bean方法

ClassPathXmlApplicationContext獲取bean實際調(diào)用了其父類AbstractApplicationContext中對應(yīng)方法。之前說過BeanFactory的核心類為DefaultListableBeanFactory姊扔,所以這里的getBeanFactory()返回的就是該類對象。除了T getBean(Class<T>)這種方式是在DefaultListableBeanFactory中提供的集惋,其他獲取bean的方法都封裝在另一個父類AbstractBeanFactory
圖2. AbstractBeanFactory中獲取bean的一系列方法

四種方式又同時抽成了T doGetBean(String, Class<T>, Object[], boolean)髓堪,該方法較長,見代碼清單1

protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {
        //      (1)
        final String beanName = transformedBeanName(name);
        Object bean;

        // Eagerly check singleton cache for manually registered singletons.
        //      (2)
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            if (logger.isDebugEnabled()) {
                if (isSingletonCurrentlyInCreation(beanName)) {
                    logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
            //      (3)
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }

        else {
            // Fail if we're already creating this bean instance:
            // We're assumably within a circular reference.
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }

            // Check if bean definition exists in this factory.
            BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                String nameToLookup = originalBeanName(name);
                if (args != null) {
                    // Delegation to parent with explicit args.
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                }
                else {
                    // No args -> delegate to standard getBean method.
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
            }

            if (!typeCheckOnly) {
                markBeanAsCreated(beanName);
            }

            try {
                final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for (String dependsOnBean : dependsOn) {
                        getBean(dependsOnBean);
                        registerDependentBean(dependsOnBean, beanName);
                    }
                }

                // Create bean instance.
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                        public Object getObject() throws BeansException {
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            catch (BeansException ex) {
                                // Explicitly remove instance from singleton cache: It might have been put there
                                // eagerly by the creation process, to allow for circular reference resolution.
                                // Also remove any beans that received a temporary reference to the bean.
                                destroySingleton(beanName);
                                throw ex;
                            }
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }

                else if (mbd.isPrototype()) {
                    // It's a prototype -> create a new instance.
                    Object prototypeInstance = null;
                    try {
                        beforePrototypeCreation(beanName);
                        prototypeInstance = createBean(beanName, mbd, args);
                    }
                    finally {
                        afterPrototypeCreation(beanName);
                    }
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }

                else {
                    String scopeName = mbd.getScope();
                    final Scope scope = this.scopes.get(scopeName);
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
                    }
                    try {
                        Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                            public Object getObject() throws BeansException {
                                beforePrototypeCreation(beanName);
                                try {
                                    return createBean(beanName, mbd, args);
                                }
                                finally {
                                    afterPrototypeCreation(beanName);
                                }
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                    catch (IllegalStateException ex) {
                        throw new BeanCreationException(beanName,
                                "Scope '" + scopeName + "' is not active for the current thread; " +
                                "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                ex);
                    }
                }
            }
            catch (BeansException ex) {
                cleanupAfterBeanCreationFailure(beanName);
                throw ex;
            }
        }

        // Check if required type matches the type of the actual bean instance.
        if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
            try {
                return getTypeConverter().convertIfNecessary(bean, requiredType);
            }
            catch (TypeMismatchException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Failed to convert bean '" + name + "' to required type [" +
                            ClassUtils.getQualifiedName(requiredType) + "]", ex);
                }
                throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
            }
        }
        return (T) bean;
    }

從整體看上面的代碼可根據(jù)最頂層的if/else分為上下兩部分搞坝,上半部分在調(diào)用getBean(String)時進入搔谴,下半部分在new ClassPathXmlApplicationContext(String[])時就會隱式執(zhí)行,下半部分進行首次初始化桩撮,上半部分從緩存中獲取敦第,通常情況隱式執(zhí)行早于顯示調(diào)用,雖然真實情況下存在誰先誰后等“時序”因素距境,但為了分析方便申尼,清楚表述,我在文章中都盡可能不考慮”時序“的影響垫桂,只關(guān)注邏輯流程师幕,由于執(zhí)行邏輯比較復(fù)雜,我們一點點分析诬滩。標(biāo)注1將name進行了規(guī)范化處理霹粥,這里的name存在多種可能:1.普通的id/name;2.bean的昵稱疼鸟;3.FactoryBean對應(yīng)的id/name后控,三種情況對應(yīng)三種不同的規(guī)范化方式,第一種不做處理直接返回空镜;第二種根據(jù)昵稱從SimpleAliasRegistry中的成員變量Map<String, String> aliasMap中獲得昵稱對應(yīng)的真實id/name返回浩淘,至于昵稱是什么時候放入aliasMap的,在Spring解析之IoC:<bean>解析及Bean的注冊的最后進行了bean的注冊吴攒,該方法的是由BeanDefinitionReaderUtils調(diào)用的

圖3. alias的注冊

下劃線處就是bean注冊的流程张抄,而紅框內(nèi)就是保存所有bean對應(yīng)昵稱的過程,根據(jù)我們之前給過的DefaultListableBeanFactory類圖可知洼怔,這里的registry就是DefaultListableBeanFactory署惯,但registerAlias(String, String)定義在父類SimpleAliasRegistry中,所有的alias就放在了該類的成員變量aliasMap中镣隶。至于第三種情況我們需要先知道FactoryBean的用法和用途极谊,FactoryBean可以看成一個實現(xiàn)了FactoryBean<T>接口的特殊bean诡右,它既可以是一個普通的bean,也可以成為生產(chǎn)另一個bean的工廠轻猖,當(dāng)創(chuàng)建一個FactoryBean后帆吻,我們可以通過getBean("FactoryBean的id/name")得到該FactoryBean生產(chǎn)的普通bean對象,如果就想得到該FactoryBean實例行不行呢蜕依?答案當(dāng)然是可以的桅锄,我們需要這么寫getBean("&FactoryBean的id/name"),下面我們舉個例子說明样眠,首先寫一個實現(xiàn)FactoryBean<T>的工廠Bean

圖4. 生產(chǎn)Student的FactoryBean

接下來在配置文件上加上對該類的配置友瘤,注意我沒有對Student類進行XML配置

圖5. 工廠Bean的配置

最后我們獲得Student實例以及對應(yīng)的生產(chǎn)工廠實例

圖6. 獲得Student實例和StudentFactoryBean實例

代碼清單1 標(biāo)注2根據(jù)beanName獲得單例對象,但這里的單例對象不一定就是我們在配置文件中定義的bean的實例檐束,為什么這么說我們進入方法看看

圖7. DefaultSingletonBeanRegistry的Object getSingleton(String, boolean)

圖中標(biāo)記出了三個不同的“緩存”辫秧,所謂的緩存就是定義在DefaultSingletonBeanRegistry的三個成員變量,該類和DefaultListableBeanFactory是父子關(guān)系被丧,借用其他文章對這三個對象的稱謂盟戏,singletonObjects為一級緩存,保存beanNamebean實例的對應(yīng)關(guān)系甥桂;earlySingletonObjects是二級緩存柿究,也保存了beanNamebean的對應(yīng)關(guān)系。我們都知道假設(shè)A對象在初始化時依賴B對象黄选,Spring會在A對象初始化前先將B對象初始化蝇摸,本文將這種實例化完成并形成依賴關(guān)系的整個過程稱為完全初始化;將沒有建立依賴關(guān)系办陷,僅僅實例化完成的過程稱為不完全初始化貌夕,那這時解釋singletonObjectsearlySingletonObjects的區(qū)別就很簡單了,前者保存了完全初始化的實例映射民镜,后者保存未完全初始化的實例映射啡专;最后一個變量singletonFactories是三級緩存,保存了beanName和接口ObjectFactory實現(xiàn)類的映射制圈,現(xiàn)在問題又來了ObjectFactory是什么東東们童?我們可以將其理解為生產(chǎn)對象的工廠,和上面剛說到的FactoryBean有幾分相似鲸鹦,之后我們會看到該類的具體實現(xiàn)和具體用法
和平時的認(rèn)知一樣慧库,三級緩存的調(diào)用依次深入,且相互排斥亥鬓,即一個beanName只會存在于一個緩存中完沪,設(shè)置如此復(fù)雜的邏輯目的是盡可能的解決循環(huán)依賴問題域庇。解釋完后再看代碼就很清楚了嵌戈,先根據(jù)beanName從一級緩存中獲取bean覆积,如果沒有取到并且該對象正在創(chuàng)建isSingletonCurrentlyInCreation(String),就去二級緩存中查找熟呛。什么叫該對象正在創(chuàng)建呢宽档?該對象正在創(chuàng)建又和循環(huán)依賴什么關(guān)系呢?舉個例子:A對象依賴B對象庵朝,B對象依賴A對象吗冤,在創(chuàng)建A對象時要先創(chuàng)建B對象,在創(chuàng)建B對象時依賴A對象九府,A創(chuàng)建了一半等待B對象椎瘟,此時A對象的狀態(tài)就是正在創(chuàng)建,對于B對象也是一樣的侄旬。如果二級緩存中依然沒有肺蔚,且允許早期引用標(biāo)識allowEarlyReference = true,就會從三級緩存中得到ObjectFactory進而創(chuàng)建出最終的對象儡羔,創(chuàng)建完成后會將該對象存放在二級緩存中
代碼清單1 標(biāo)注3才是獲得真正bean的方法

圖8. AbstractBeanFactory的getObjectForBeanInstance(Object, String, String, RootBeanDefinition)

注意方法上注解對于傳參的解釋宣羊,name是沒有進過規(guī)范化處理的名稱,那么其中可能包含&汰蜘,可能是昵稱仇冯,也可能就是普通的beanName。第一處判斷name是否包含工廠bean前綴&beanInstance不屬于FactoryBean族操,這種情況是不可能存在的苛坚,所以拋出異常;第二處判斷過濾掉兩種情況:1.普通的bean坪创,前半部分為true炕婶,直接返回;2.只想獲得FactoryBean莱预,后半部分為true柠掂,直接返回。只有想通過FactoryBean獲得普通bean時才程序才會繼續(xù)往下執(zhí)行
另外我們需要說一下RootBeanDefinition依沮,之前在分析配置加載解析的過程中bean對應(yīng)的實體一直為GenericBeanDefinition涯贞,而這里又出來個RootBeanDefinition,該類與前者不同之處在于危喉,前者可以稱為一般意義上的bean宋渔,后者主要用于封裝存在繼承關(guān)系的bean對象,比如<bean>中存在parent屬性就意味著存在rootchild的關(guān)系辜限,在解析的時候Spring會將所有的父子關(guān)系merge到一個BeanDefinition中皇拣,而RootBeanDefinition就能很好的表示bean直接的父子關(guān)系。當(dāng)然現(xiàn)在Spring已經(jīng)推薦使用GenericBeanDefinition,該類同樣可以封裝父子關(guān)系氧急,至于為什么RootBeanDefinition依然在使用颗胡,我覺得可能是為了兼容老版本的原因吧
上面說過,既然流程走到標(biāo)注3處說明此時我們使用FactoryBeanid/name要獲得生產(chǎn)的bean吩坝,此時首先從Map<String, Object> factoryBeanObjectCache緩存中獲取毒姨,其中keyFactoryBean namevalue為根據(jù)FactoryBean生產(chǎn)的對應(yīng)bean钉寝,緩存中已經(jīng)存在直接返回弧呐,沒有繼續(xù)往下
圖9. AbstractBeanFactory的getMergedLocalBeanDefinition(String)

繁瑣的Spring又出現(xiàn)一個緩存mergedBeanDefinitionkey為標(biāo)簽id/name嵌纲,value保存對應(yīng)RootBeanDefinition俘枫,存在RootBeanDefinition直接返回,但要注意點逮走,經(jīng)過上兩篇文章的分析崩哩,此時在DefaultListableBeanFactory中的緩存beanDefinitionMap中已經(jīng)存在beanNameBeanDefinition的映射了,所以圖中的getBeanDefinition(String)實際上會返回beanName對應(yīng)的GenericBeanDefinition對象言沐,最后調(diào)用RootBeanDefinition getMergedBeanDefinition(String, BeanDefinition, BeanDefinition)邓嘹,上面說過<bean>可能存在父子關(guān)系,在該方法中如果當(dāng)前BeanDefinition為子對象险胰,會根據(jù)parentName得到對應(yīng)的父BeanDefinition汹押,并通過遞歸調(diào)用的方式將父BeanDefinition融合進子BeanDefinition,最后包裝成RootBeanDefinition返回起便,該方法中又是一坨惡心的邏輯棚贾,這里就不挨個細(xì)聊,有興趣的讀者可以繼續(xù)深入榆综。圖8標(biāo)注5是根據(jù)FactoryBean名稱獲取bean的入口

圖10. 從FactoryBean獲得對應(yīng)的bean

首先根據(jù)beanName判斷該FactoryBean(由前面的分析可知此時beanName只可能是FactoryBean自定義實現(xiàn)類的id/name)是否單例妙痹,是否存在于緩存singletonObjects中,不滿足直接調(diào)用核心方法Object doGetObjectFromFactoryBean(FactoryBean<?>, String, boolean)生成對應(yīng)bean鼻疮,否則再嘗試從factoryBeanObjectCache中獲取bean怯伊,上面說過該緩存內(nèi)是FactoryBean name和生成bean的映射,如果映射不存在依然調(diào)用Object doGetObjectFromFactoryBean(FactoryBean<?>, String, boolean)判沟,并更新factoryBeanObjectCache耿芹,因此最后核心創(chuàng)建bean的職責(zé)就落在了紅框內(nèi)
圖11. FactoryBeanRegistrySupport的Object doGetObjectFromFactoryBean(FactoryBean<?>, String, boolean)

忽略其他不相干的部分,只關(guān)注所畫代碼邏輯還是非常清楚的挪哄,直接factory.getObject()多態(tài)執(zhí)行我們自定義FactoryBeangetObject()吧秕,如果存在自定義的BeanPostProcessor會進入標(biāo)注2處代碼,我們還是以例子作為切入點講解迹炼。寫一個自定義后處理器首先需要實現(xiàn)BeanPostProcessor接口
圖12. 自定義BeanPostProcessorImpl

接口有兩個待實現(xiàn)方法砸彬,好理解的是該處理器肯定是在bean實例創(chuàng)建完成之后調(diào)用的,但是這里的before/after Initialization指的是是什么呢?我們的bean依然是Student砂碉,但是該類實現(xiàn)了InitializingBeanafterPropertiesSet()方法吟秩,該方法可供創(chuàng)建對象時進行一些初始化的行為,我們分別在Student的構(gòu)造器和afterPropertiesSet()內(nèi)打印一句話
圖13. Student對象

這里擴充點知識绽淘,Spring提供了三種方式讓程序員在對象創(chuàng)建和銷毀時進行一些自定義的處理:1.@PostConstruct@PreDestory;2.標(biāo)簽中定義init-methoddestory-method闹伪;3.實現(xiàn)InitializingBeanDisposableBean沪铭,即便都是初始化/銷毀方式內(nèi)部執(zhí)行的順序也是有不同的,具體的使用方式和差異請讀者自行研究偏瓤。但是上面所說的before/after Initialization就是基于最后一種方式來說的杀怠。我們在XML上配置好自定義bean后處理器和Student運行一下,結(jié)果如下
圖14. bean后處理例子順序問題

結(jié)果很明顯最先執(zhí)行的是Student的構(gòu)造器厅克,而后為自定義bean后處理器的postProcessBeforeInitialization()赔退,實現(xiàn)的afterPropertiesSet()第三,最后是postProcessAfterInitialization()证舟。這里例子中Spring創(chuàng)建了普通的bean硕旗,如果使用FactoryBean來生成Student對象又會發(fā)生什么呢?答案在BeanPostProcessor的注解中寫的很明白
圖14. BeanPostProcessor接口注解

對于使用自定義FactoryBean創(chuàng)建bean的流程來說postProcessAfterInitialization()方法會調(diào)用兩次女责,一次是實例化自定義FactoryBean時漆枚,第二次是FactoryBean.getObject()創(chuàng)建bean時。解釋完bean后處理器的使用和觸發(fā)順序我們再來看Spring是如何調(diào)用我們自定的后處理器抵知,圖12標(biāo)注2就是其中一個調(diào)用點墙基,此時thisDefaultListableBeanFactory的實例,獲取所有自定后處理器并調(diào)用的方法封裝在DefaultListableBeanFactory的父類AbstractAutowireCapableBean
圖15. AbstractAutowireCapableBean的Object applyBeanPostProcessorsAfterInitialization(Object, String)

List<BeanPostProcessor> beanPostProcessors中遍歷每一個自定義后處理器刷喜,依次調(diào)用postProcessAfterInitialization(Object, String)残制,大家可能會感到奇怪,為什么只調(diào)用postProcessAfterInitialization而沒有調(diào)用postProcessBeforeInitialization呢掖疮,其實呢上面我們說過對于使用自定義FactoryBean創(chuàng)建bean流程來說postProcessAfterInitialization會被調(diào)用兩次初茶,這里實際上就是第二次對于生產(chǎn)出來的bean做的后處理,而第一次調(diào)用在哪浊闪?postProcessBeforeInitialization的調(diào)用在哪纺蛆?答案是在new ClassPathXmlApplicationContext(String[])中隱式調(diào)用過了,也就是文章開始說過的“下半部分”處進行了調(diào)用规揪,如果大家還懵逼的話桥氏,下一篇分析”下半部分“就清楚了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猛铅,隨后出現(xiàn)的幾起案子字支,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堕伪,死亡現(xiàn)場離奇詭異揖庄,居然都是意外死亡,警方通過查閱死者的電腦和手機欠雌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門蹄梢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人富俄,你說我怎么就攤上這事禁炒。” “怎么了霍比?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵幕袱,是天一觀的道長。 經(jīng)常有香客問我悠瞬,道長们豌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任浅妆,我火速辦了婚禮望迎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凌外。我一直安慰自己擂煞,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布趴乡。 她就那樣靜靜地躺著对省,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晾捏。 梳的紋絲不亂的頭發(fā)上蒿涎,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音惦辛,去河邊找鬼劳秋。 笑死,一個胖子當(dāng)著我的面吹牛胖齐,可吹牛的內(nèi)容都是我干的玻淑。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼呀伙,長吁一口氣:“原來是場噩夢啊……” “哼补履!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起剿另,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤箫锤,失蹤者是張志新(化名)和其女友劉穎贬蛙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谚攒,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡阳准,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了馏臭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片野蝇。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖括儒,靈堂內(nèi)的尸體忽然破棺而出绕沈,到底是詐尸還是另有隱情,我是刑警寧澤塑崖,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站痛倚,受9級特大地震影響规婆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝉稳,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一抒蚜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耘戚,春花似錦嗡髓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至撞秋,卻和暖如春长捧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吻贿。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工串结, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舅列。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓肌割,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帐要。 傳聞我的和親對象是個殘疾皇子把敞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容