springboot啟動解析六

此篇文章繼續(xù)解析SpringApplication的run方法具體代碼如下:

public ConfigurableApplicationContext run(String... args) {
        1.StopWatch stopWatch = new StopWatch();
        2.stopWatch.start();
        3.ConfigurableApplicationContext context = null;
        4.Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        5.configureHeadlessProperty();
        6.SpringApplicationRunListeners listeners = getRunListeners(args);
        7.listeners.starting();
        try {
        8.  ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
        9.  ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
        10. configureIgnoreBeanInfo(environment);
        11. Banner printedBanner = printBanner(environment);
        12. context = createApplicationContext();
        13. exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
        14.     prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
        15. refreshContext(context);
        16. afterRefresh(context, applicationArguments);
        17. stopWatch.stop();
        18. if (this.logStartupInfo) {
        19.     new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
        20. listeners.started(context);
        21. callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
        22. handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
        23. listeners.running(context);
        }
        catch (Throwable ex) {
        24. handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

一共24行代碼誊锭,本章主要解析15小節(jié),其就是真正的把spring容器初始化的方法,基本上把spring容器的大部分功能都包含在內(nèi)

    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }
最終注冊方法就是這個doClose
protected void doClose() {
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            if (logger.isInfoEnabled()) {
                logger.info("Closing " + this);
            }
卸載當(dāng)前的spring容器壁袄,包含卸載該spring容器相關(guān)的MBeanServer
            LiveBeansView.unregisterApplicationContext(this);

            try {
    發(fā)布關(guān)閉事件
                publishEvent(new ContextClosedEvent(this));
            }
            catch (Throwable ex) {
                logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
            }

            停止所有實現(xiàn)SmartLifecycle或者Lifecycle的bean出吹,lifecycleProcessor 就是
負(fù)責(zé)這些bean的聲明周期
            if (this.lifecycleProcessor != null) {
                try {
                    this.lifecycleProcessor.onClose();
                }
                catch (Throwable ex) {
                    logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
                }
            }

        摧毀容器中的所有單例bean
            destroyBeans();

        關(guān)閉bean工廠
            closeBeanFactory();

        關(guān)閉我們內(nèi)置的tomcat的容器
            onClose();
設(shè)置未激活標(biāo)識
            this.active.set(false);
        }

refresh(context);的詳細(xì)代碼如下:

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
        清除ClassPathBeanDefinitionScanner的緩存
            設(shè)置servletContext和servletConfig耕陷,如果這個兩個屬性不為空則替換
environment的source中關(guān)于這個兩個的屬性
校驗必須存在的properties是否存在(我們可以設(shè)置哪些properties)
設(shè)置earlyApplicationEvents掂名,存儲早期的ApplicationEvents,當(dāng)所有的ApplicationContextListener都注冊進(jìn)入容器之后在執(zhí)行這些事件

            prepareRefresh();

        獲取beanFactory哟沫,還給其設(shè)置了refreshed標(biāo)識
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

準(zhǔn)備beanFactory饺蔑,在注冊bean之前先給beanFactory注冊一堆bean,這些bean可以
幫助beanFactory后期去解析嗜诀,注冊其他的bean猾警,比如忽略依賴,比如指定依賴
ignoreDependencyInterface:當(dāng)我們spring指定自動注入的時候會忽略對這個指定參數(shù)的EnvironmentAware的注入
registerResolvableDependency:當(dāng)我們spring指定自動注入的時候會只注入我們指定的參數(shù)屬性
            prepareBeanFactory(beanFactory);

            try {
        設(shè)置ignoreDependencyInterface(ServletContextAware.class)隆敢,標(biāo)識對于ServletContextAware
的實現(xiàn)類中的ServletContextAware屬性禁止自動注入
如果存在basePackages和annotatedClasses发皿,則掃描basePackages,并把掃描到的class和annotatedClasses注冊到spring容器中
                postProcessBeanFactory(beanFactory);

            執(zhí)行beanFactory的PostProcessors
按照這個順序PriorityOrdered拂蝎,Ordered穴墅,nonOrdered 
分別執(zhí)行BeanDefinitionRegistry的postProcessBeanDefinitionRegistry,
BeanFactoryPostProcessor的postProcessBeanFactory

alreadyCreated:這個集合存儲已經(jīng)初始化好的bean實例,
所以當(dāng)我們執(zhí)行完beanFactoryPostProcessor的方法時候 會清除未初始化的bean
也會清除allBeanNamesByType和singletonBeanNamesByType

                invokeBeanFactoryPostProcessors(beanFactory);

注冊beanPostProcessor(會確保spring的internalPostProcessors)一定是排在beanPostProcessor的集合最后
                registerBeanPostProcessors(beanFactory);

    給spring的容器和其父容器設(shè)置messageSource
                initMessageSource();

        給spring的容器注冊一個事件廣播器applicationEventMulticaster
                initApplicationEventMulticaster();

        初始化tomcat
                onRefresh();

        注冊所有的ApplicationListener,并且執(zhí)行之前積攢的earlyApplicationEvents
                registerListeners();

            初始化所有非懶加載的單例bean
                finishBeanFactoryInitialization(beanFactory);

清理resource的緩存玄货,初始化LifecycleProcessor用來執(zhí)行
lifeCycle的bean的onRefresh皇钞,
發(fā)布ContextRefreshedEvent,將該spring容器注冊到
LiveBeansView松捉,啟動tomcat發(fā)布tomcat啟動事件
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

每行代碼的意思都有英文解釋夹界,我們一行行來分析
prepareRefresh();的詳細(xì)代碼如下:

    protected void prepareRefresh() {
        this.scanner.clearCache();
        super.prepareRefresh();
    }

    protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }

        // Initialize any placeholder property sources in the context environment
        initPropertySources();

        // Validate that all properties marked as required are resolvable
        // see ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();

        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }

第一步主要是父類AbstractApplicationContext的refresh方法(因為其子類AnnotationConfigServletWebServerApplicationContext 沒有實現(xiàn)該方法)
refresh:

  • 該方法第一步調(diào)用子類的 prepareRefresh()方法
  • 子類的prepareRefresh方法清除CachingMetadataReaderFactory中的緩存然后繼續(xù)調(diào)用父類的prepareRefresh方法
    prepareRefresh:
  • 設(shè)置相關(guān)屬性 校驗environment的properties是否正確
  • initPropertySources會調(diào)用GenericWebApplicationContext的實現(xiàn) 就是設(shè)置容器的環(huán)境初始化容器的屬性
  • earlyApplicationEvents 是在multicaster 可用的時候進(jìn)行發(fā)布
    obtainFreshBeanFactory()的代碼如下:
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

    protected final void refreshBeanFactory() throws IllegalStateException {
        if (!this.refreshed.compareAndSet(false, true)) {
            throw new IllegalStateException(
                    "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
        }
        this.beanFactory.setSerializationId(getId());
    }

    public final ConfigurableListableBeanFactory getBeanFactory() {
        return this.beanFactory;
    }
  • 首先ConfigurableListableBeanFactory 是在初始化容器的時候其父類容器也被初始化了才賦值的
  • spring容器是通過ConfigurableListableBeanFactory 這個bean工廠進(jìn)行真正的bean加載
  • 然后就是首先查看beanFactory是否已經(jīng)refresh過,如果是 就給容器一個id 同時返回容器
    prepareBeanFactory(beanFactory);的詳細(xì)代碼如下:
    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // Tell the internal bean factory to use the context's class loader etc.
        beanFactory.setBeanClassLoader(getClassLoader());
        beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

        // Configure the bean factory with context callbacks.
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
        beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
        beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
        beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
        beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

        // BeanFactory interface not registered as resolvable type in a plain factory.
        // MessageSource registered (and found for autowiring) as a bean.
        beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
        beanFactory.registerResolvableDependency(ResourceLoader.class, this);
        beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
        beanFactory.registerResolvableDependency(ApplicationContext.class, this);

        // Register early post-processor for detecting inner beans as ApplicationListeners.
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

        // Detect a LoadTimeWeaver and prepare for weaving, if found.
        if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            // Set a temporary ClassLoader for type matching.
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }

        // Register default environment beans.
        if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }
    }
  • 上述代碼的作用如下:
  • 設(shè)置beanFactory的classloader隘世,一般都是線程上下文類加載器
  • 設(shè)置beanFactory的BeanExpressionResolver可柿,用來根據(jù)bean的表達(dá)式來解析bean
  • 增加ResourceEditorRegistrar,主要是注冊用戶或者系統(tǒng)定義的屬性編輯器
  • 添加BeanPostProcessor-ApplicationContextAwareProcessor
    1.BeanPostProcessor 提供了2個方法
    postProcessBeforeInitialization(該方法在調(diào)用bean的初始化方法(注意是初始化方法此時bean早已經(jīng)實例化好了))
    postProcessAfterInitialization(該方法是在調(diào)用bean的初始化方法后進(jìn)行調(diào)用)
    還需注意的是@service @component 都沒有初始化方法 但是@Bean注解有
    還有需要注意的是若是上述2中方法對bean 進(jìn)行修改那么原始的bean就會被替換
    而ApplicationContextAwareProcessor則是只修改postProcessBeforeInitialization丙者,
    方法內(nèi)部就是判斷 bean是否屬于
    EnvironmentAware
    EmbeddedValueResolverAware
    ResourceLoaderAware
    ApplicationEventPublisherAware
    MessageSourceAware
    ApplicationContextAware
    不同的aware調(diào)用不同的方法
    代碼細(xì)節(jié)如下:
    private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof EnvironmentAware) {
                ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
            }
            if (bean instanceof EmbeddedValueResolverAware) {
                ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
            }
            if (bean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
            }
            if (bean instanceof ApplicationEventPublisherAware) {
                ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
            }
            if (bean instanceof MessageSourceAware) {
                ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
            }
            if (bean instanceof ApplicationContextAware) {
                ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
            }
        }
    }

而ignoreDependencyInterface 則是正好對應(yīng)著上述的
EnvironmentAware
EmbeddedValueResolverAware
ResourceLoaderAware
ApplicationEventPublisherAware
MessageSourceAware
ApplicationContextAware
--ignoreDependencyInterface的真正意思是在自動裝配時忽略指定接口的實現(xiàn)類中复斥,對外的依賴。
如果想忽略指定類的自動注入使用ignoreDependencyType

  • autowiring特定指的是通過beans標(biāo)簽default-autowire 即@Bean注解

蔓钟,發(fā)現(xiàn)英語中的autowiring特定指的是通過beans標(biāo)簽default-autowire屬性來依賴注入的方式永票,而不是指使用@Autowired注解進(jìn)行的依賴注入。區(qū)別在于滥沫,使用default-autowire會自動給所有的類都會從容器中查找匹配的依賴并注入,而使用@Autowired注解只會給這些注解的對象從容器查找依賴并注入键俱。

  • 所謂的自動裝配是指我們配置ben的xml 或者@Bean的時候 會自動幫我們注入屬性(比如我們配置A類的B屬性=3)兰绣,那么容器就會給我們生成一個屬性為3的A的實例對象
    如果上述幾個Aware不使用ignoreDependencyInterface會有什么問題尼,很簡單 如果我們寫一個實現(xiàn)類编振,然后給他默認(rèn)配置ApplicationContext屬性缀辩,而這個ApplicationContext是我們自己new的,這就導(dǎo)致了我們使用的不是spring自己生成的ApplicationContext踪央,所以spring為了避免我們手誤自己注冊下這個屬性臀玄,而幫我們忽略。
    而registerResolvableDependency意思更簡單了
    beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
    上述代碼的意思就是當(dāng)我們使用自動注入比如@AutoWired注入BeanFactory 類型的那么他會幫我們注入我們目前輸入的實例對象畅蹂,而不會去注入他的其他實現(xiàn)類健无,這是為了避免我們使用其他的自定義的實例對象
    ApplicationListenerDetector是檢測ApplicationListener的實現(xiàn)類是否是單例
    // Detect a LoadTimeWeaver and prepare for weaving, if found.
        if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            // Set a temporary ClassLoader for type matching.
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }

上述檢測是否存在代碼織入,簡介如下抄襲別人博客的定義:

在Java 語言中液斜,從織入切面的方式上來看累贤,存在三種織入方式:編譯期織入、類加載期織入和運行期織入少漆。編譯期織入是指在Java編譯期臼膏,采用特殊的編譯器,將切面織入到Java類中示损;而類加載期織入則指通過特殊的類加載器渗磅,在類字節(jié)碼加載到JVM時,織入切面;運行期織入則是采用CGLib工具或JDK動態(tài)代理進(jìn)行切面的織入始鱼。

AspectJ采用編譯期織入和類加載期織入的方式織入切面仔掸,是語言級的AOP實現(xiàn),提供了完備的AOP支持风响。它用AspectJ語言定義切面嘉汰,在編譯期或類加載期將切面織入到Java類中。

AspectJ提供了兩種切面織入方式状勤,第一種通過特殊編譯器鞋怀,在編譯期,將AspectJ語言編寫的切面類織入到Java類中持搜,可以通過一個Ant或Maven任務(wù)來完成這個操作密似;第二種方式是類加載期織入,也簡稱為LTW(Load Time Weaving)

如何使用Load Time Weaving葫盼?首先残腌,需要通過JVM的-javaagent參數(shù)設(shè)置LTW的織入器類包,以代理JVM默認(rèn)的類加載器贫导;第二抛猫,LTW織入器需要一個 aop.xml文件,在該文件中指定切面類和需要進(jìn)行切面織入的目標(biāo)類孩灯。

本人對上述LTW似懂非懂
下面的代碼就沒什么好講的了闺金,就是檢測某個屬性是否存在,然后注冊單例bean

if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }

refresh中的postProcessBeanFactory 等以下代碼留著明天分享

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末峰档,一起剝皮案震驚了整個濱河市败匹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讥巡,老刑警劉巖掀亩,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異欢顷,居然都是意外死亡槽棍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門吱涉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刹泄,“玉大人,你說我怎么就攤上這事怎爵√厥” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵鳖链,是天一觀的道長姆蘸。 經(jīng)常有香客問我墩莫,道長,這世上最難降的妖魔是什么逞敷? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任狂秦,我火速辦了婚禮,結(jié)果婚禮上推捐,老公的妹妹穿的比我還像新娘裂问。我一直安慰自己,他們只是感情好牛柒,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布堪簿。 她就那樣靜靜地躺著,像睡著了一般皮壁。 火紅的嫁衣襯著肌膚如雪椭更。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天蛾魄,我揣著相機(jī)與錄音虑瀑,去河邊找鬼。 笑死滴须,一個胖子當(dāng)著我的面吹牛舌狗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扔水,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼把夸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铭污?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤膀篮,失蹤者是張志新(化名)和其女友劉穎嘹狞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體誓竿,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡磅网,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了筷屡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涧偷。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖毙死,靈堂內(nèi)的尸體忽然破棺而出燎潮,到底是詐尸還是另有隱情,我是刑警寧澤扼倘,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布确封,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏爪喘。R本人自食惡果不足惜颜曾,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秉剑。 院中可真熱鬧泛豪,春花似錦、人聲如沸侦鹏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽种柑。三九已至岗仑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聚请,已是汗流浹背荠雕。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留驶赏,地道東北人炸卑。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像煤傍,于是被迫代替她去往敵國和親盖文。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

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