Spring IOC原理總結

Spring容器高層視圖

Spring 啟動時讀取應用程序提供的Bean配置信息,并在Spring容器中生成一份相應的Bean配置注冊表,然后根據這張注冊表實例化Bean,裝配好Bean之間的依賴關系,為上層應用提供準備就緒的運行環境。

Bean緩存池:HashMap實現

IOC容器介紹

Spring 通過一個配置文件描述 Bean 及 Bean 之間的依賴關系,利用 Java 語言的反射功能實例化 Bean 并建立 Bean 之間的依賴關系。 Spring 的 IoC 容器在完成這些底層工作的基礎上,還提供了 Bean 實例緩存、生命周期管理、 Bean 實例代理、事件發布、資源裝載等高級服務。

BeanFactory 是 Spring 框架的基礎設施,面向 Spring 本身;

ApplicationContext 面向使用 Spring 框架的開發者,幾乎所有的應用場合我們都直接使用 ApplicationContext 而非底層的 BeanFactory。

BeanFactory

BeanFactory體系架構:

BeanDefinitionRegistry: Spring 配置文件中每一個節點元素在 Spring 容器里都通過一個 BeanDefinition 對象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注冊 BeanDefinition 對象的方法。

BeanFactory 接口位于類結構樹的頂端 ,它最主要的方法就是 getBean(String beanName),該方法從容器中返回特定名稱的 Bean,BeanFactory 的功能通過其他的接口得到不斷擴展:

ListableBeanFactory:該接口定義了訪問容器中 Bean 基本信息的若干方法,如查看Bean 的個數、獲取某一類型 Bean 的配置名、查看容器中是否包括某一 Bean 等方法;

HierarchicalBeanFactory:父子級聯 IoC 容器的接口,子容器可以通過接口方法訪問父容器; 通過 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子層級關聯的容器體系,子容器可以訪問父容器中的 Bean,但父容器不能訪問子容器的 Bean。Spring 使用父子容器實現了很多功能,比如在 Spring MVC 中,展現層 Bean 位于一個子容器中,而業務層和持久層的 Bean 位于父容器中。這樣,展現層 Bean 就可以引用業務層和持久層的 Bean,而業務層和持久層的 Bean 則看不到展現層的 Bean。

ConfigurableBeanFactory:是一個重要的接口,增強了 IoC 容器的可定制性,它定義了設置類裝載器、屬性編輯器、容器初始化后置處理器等方法;

AutowireCapableBeanFactory:定義了將容器中的 Bean 按某種規則(如按名字匹配、按類型匹配等)進行自動裝配的方法;

SingletonBeanRegistry:定義了允許在運行期間向容器注冊單實例 Bean 的方法;

例子:

使用 Spring 配置文件為 Car 提供配置信息:beans.xml:

通過 BeanFactory 裝載配置文件,啟動 Spring IoC 容器:

XmlBeanFactory 通過 Resource 裝載 Spring 配置信息并啟動 IoC 容器,然后就可以通過 BeanFactory#getBean(beanName)方法從 IoC 容器中獲取 Bean 了。通過 BeanFactory 啟動IoC 容器時,并不會初始化配置文件中定義的 Bean,初始化動作發生在第一個調用時。

對于單實例( singleton)的 Bean 來說,BeanFactory會緩存 Bean 實例,所以第二次使用 getBean() 獲取 Bean 時將直接從 IoC 容器的緩存中獲取 Bean 實例。Spring 在 DefaultSingletonBeanRegistry 類中提供了一個用于緩存單實例 Bean 的緩存器,它是一個用HashMap 實現的緩存器,單實例的 Bean 以 beanName 為鍵保存在這個HashMap 中。

值得一提的是,在初始化 BeanFactory 時,必須為其提供一種日志框架,比如使用Log4J, 即在類路徑下提供 Log4J 配置文件,這樣啟動 Spring 容器才不會報錯。

ApplicationContext

ApplicationContext 由 BeanFactory 派生而來,提供了更多面向實際應用的功能。

在BeanFactory 中,很多功能需要以編程的方式實現,而在 ApplicationContext 中則可以通過配置的方式實現。

ApplicationContext 繼承了 HierarchicalBeanFactory 和 ListableBeanFactory 接口,在此基礎上,還通過多個其他的接口擴展了 BeanFactory 的功能:

ClassPathXmlApplicationContext:默認從類路徑加載配置文件

FileSystemXmlApplicationContext:默認從文件系統中裝載配置文件

ApplicationEventPublisher:讓容器擁有發布應用上下文事件的功能,包括容器啟動事件、關閉事件等。實現了 ApplicationListener 事件監聽接口的 Bean 可以接收到容器事件 , 并對事件進行響應處理 。 在 ApplicationContext 抽象實現類AbstractApplicationContext 中,我們可以發現存在一個 ApplicationEventMulticaster,它負責保存所有監聽器,以便在容器產生上下文事件時通知這些事件監聽者。

MessageSource:為應用提供 i18n 國際化消息訪問的功能;

ResourcePatternResolver : 所 有 ApplicationContext 實現類都實現了類似于PathMatchingResourcePatternResolver 的功能,可以通過帶前綴的 Ant 風格的資源文件路徑裝載 Spring 的配置文件。

LifeCycle:該接口是 Spring 2.0 加入的,該接口提供了 start()和 stop()兩個方法,主要用于控制異步處理過程。在具體使用時,該接口同時被 ApplicationContext 實現及具體 Bean 實現, ApplicationContext 會將 start/stop 的信息傳遞給容器中所有實現了該接口的 Bean,以達到管理和控制 JMX、任務調度等目的。

ConfigurableApplicationContext 擴展于 ApplicationContext,它新增加了兩個主要的方法: refresh()和 close(),讓 ApplicationContext 具有啟動、刷新和關閉應用上下文的能力。在應用上下文關閉的情況下調用 refresh()即可啟動應用上下文,在已經啟動的狀態下,調用 refresh()則清除緩存并重新裝載配置信息,而調用close()則可關閉應用上下文。這些接口方法為容器的控制管理帶來了便利,但作為開發者,我們并不需要過多關心這些方法。

使用:

如果配置文件放置在類路徑下,用戶可以優先使用 ClassPathXmlApplicationContext 實現類:

如果配置文件放置在文件系統的路徑下,則可以優先考慮使用 FileSystemXmlApplicationContext 實現類:

Spring 3.0 支持基于類注解的配置方式,主要功能來自于 Spring 的一個名為 JavaConfig 子項目,目前 JavaConfig已經升級為 Spring核心框架的一部分。

ApplicationContext 在初始化應用上下文時就實例化所有單實例的 Bean。

WebApplicationContext

WebApplication體系架構:

WebApplicationContext 是專門為 Web 應用準備的,它允許從相對于 Web 根目錄的路徑中裝載配置文件完成初始化工作。從WebApplicationContext 中可以獲得 ServletContext 的引用,整個 Web 應用上下文對象將作為屬性放置到 ServletContext 中,以便 Web 應用環境可以訪問 Spring 應用上下文。 WebApplicationContext 定義了一個常量ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,在上下文啟動時, WebApplicationContext 實例即以此為鍵放置在 ServletContext 的屬性列表中,因此我們可以直接通過以下語句從 Web 容器中獲取WebApplicationContext:

Spring 和 Web 應用的上下文融合:

WebApplicationContext 的初始化方式:WebApplicationContext 需要 ServletContext 實例,它必須在擁有 Web 容器的前提下才能完成啟動的工作。可以在 web.xml 中配置自啟動的 Servlet 或定義 Web 容器監聽器( ServletContextListener),借助這兩者中的任何一個就可以完成啟動 Spring Web 應用上下文的工作。Spring 分別提供了用于啟動 WebApplicationContext 的 Servlet 和 Web 容器監聽器:

org.springframework.web.context.ContextLoaderServlet;

org.springframework.web.context.ContextLoaderListener

由于 WebApplicationContext 需要使用日志功能,比如日志框架使用Log4J,用戶可以將 Log4J 的配置文件放置到類路徑 WEB-INF/classes 下,這時 Log4J 引擎即可順利啟動。如果 Log4J 配置文件放置在其他位置,用戶還必須在 web.xml 指定 Log4J 配置文件位置。

Bean的生命周期

1.當調用者通過 getBean(beanName)向容器請求某一個 Bean 時,如果容器注冊了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor 接口,在實例化 Bean 之前,將調用接口的 postProcessBeforeInstantiation()方法;

2.根據配置情況調用 Bean 構造函數或工廠方法實例化 Bean;

3.如果容器注冊了 InstantiationAwareBeanPostProcessor 接口,在實例化 Bean 之后,調用該接口的 postProcessAfterInstantiation()方法,可在這里對已經實例化的對象進行一些“梳妝打扮”;

4.如果 Bean 配置了屬性信息,容器在這一步著手將配置值設置到 Bean 對應的屬性中,不過在設置每個屬性之前將先調用InstantiationAwareBeanPostProcessor 接口的postProcessPropertyValues()方法;

5.調用 Bean 的屬性設置方法設置屬性值;

6.如果 Bean 實現了 org.springframework.beans.factory.BeanNameAware 接口,將調用setBeanName()接口方法,將配置文件中該 Bean 對應的名稱設置到 Bean 中;

7.如果 Bean 實現了 org.springframework.beans.factory.BeanFactoryAware 接口,將調用 setBeanFactory()接口方法,將 BeanFactory 容器實例設置到 Bean 中;

8.如果 BeanFactory 裝配了 org.springframework.beans.factory.config.BeanPostProcessor后處理器,將調用 BeanPostProcessor 的 Object postProcessBeforeInitialization(Object bean, String beanName)接口方法對 Bean 進行加工操作。其中入參 bean 是當前正在處理的 Bean,而 beanName 是當前 Bean 的配置名,返回的對象為加工處理后的 Bean。用戶可以使用該方法對某些 Bean 進行特殊的處理,甚至改變 Bean 的行為, BeanPostProcessor 在 Spring 框架中占有重要的地位,為容器提供對 Bean 進行后續加工處理的切入點, Spring 容器所提供的各種“神奇功能”(如 AOP,動態代理等)都通過 BeanPostProcessor 實施;

9.如果 Bean 實現了 InitializingBean 的接口,將調用接口的 afterPropertiesSet()方法;

10.如果在通過 init-method 屬性定義了初始化方法,將執行這個方法;

11.BeanPostProcessor 后處理器定義了兩個方法:其一是 postProcessBeforeInitialization() 在第 8 步調用;其二是 Object postProcessAfterInitialization(Object bean, String beanName)方法,這個方法在此時調用,容器再次獲得對 Bean 進行加工處理的機會;

12.如果在中指定 Bean 的作用范圍為 scope=“prototype”,將 Bean 返回給調用者,調用者負責 Bean 后續生命的管理, Spring 不再管理這個 Bean 的生命周期。如果作用范圍設置為 scope=“singleton”,則將 Bean 放入到 Spring IoC 容器的緩存池中,并將 Bean引用返回給調用者, Spring 繼續對這些 Bean 進行后續的生命管理;

13.對于 scope=“singleton”的 Bean,當容器關閉時,將觸發 Spring 對 Bean 的后續生命周期的管理工作,首先如果 Bean 實現了 DisposableBean 接口,則將調用接口的afterPropertiesSet()方法,可以在此編寫釋放資源、記錄日志等操作;

14.對于 scope=“singleton”的 Bean,如果通過的 destroy-method 屬性指定了 Bean 的銷毀方法, Spring 將執行 Bean 的這個方法,完成 Bean 資源的釋放等操作。

可以將這些方法大致劃分為三類:

Bean 自身的方法:如調用 Bean 構造函數實例化 Bean,調用 Setter 設置 Bean 的屬性值以及通過的 init-method 和 destroy-method 所指定的方法;

Bean 級生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,這些接口方法由 Bean 類直接實現;

容器級生命周期接口方法:在上圖中帶“★” 的步驟是由 InstantiationAwareBean PostProcessor 和BeanPostProcessor 這兩個接口實現,一般稱它們的實現類為“ 后處理器” 。 后處理器接口一般不由 Bean 本身實現,它們獨立于 Bean,實現類以容器附加裝置的形式注冊到 Spring 容器中并通過接口反射為 Spring 容器預先識別。當Spring 容器創建任何 Bean 的時候,這些后處理器都會發生作用,所以這些后處理器的影響是全局性的。當然,用戶可以通過合理地編寫后處理器,讓其僅對感興趣Bean 進行加工處理

ApplicationContext 和 BeanFactory 另一個最大的不同之處在于:ApplicationContext會利用 Java 反射機制自動識別出配置文件中定義的 BeanPostProcessor、 InstantiationAwareBeanPostProcessor 和 BeanFactoryPostProcessor,并自動將它們注冊到應用上下文中;而后者需要在代碼中通過手工調用 addBeanPostProcessor()方法進行注冊。這也是為什么在應用開發時,我們普遍使用 ApplicationContext 而很少使用 BeanFactory 的原因之一

IOC容器工作機制

容器啟動過程

web環境下Spring容器、SpringMVC容器啟動過程:

首先,對于一個web應用,其部署在web容器中,web容器提供其一個全局的上下文環境,這個上下文就是ServletContext,其為后面的spring IoC容器提供宿主環境;

其次,在web.xml中會提供有contextLoaderListener(或ContextLoaderServlet)。在web容器啟動時,會觸發容器初始化事件,此時contextLoaderListener會監聽到這個事件,其contextInitialized方法會被調用,在這個方法中,spring會初始化一個啟動上下文,這個上下文被稱為根上下文,即WebApplicationContext,這是一個接口類,確切的說,其實際的實現類是XmlWebApplicationContext。這個就是spring的IoC容器,其對應的Bean定義的配置由web.xml中的context-param標簽指定。在這個IoC容器初始化完畢后,spring容器以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE為屬性Key,將其存儲到ServletContext中,便于獲取;

再次,contextLoaderListener監聽器初始化完畢后,開始初始化web.xml中配置的Servlet,這個servlet可以配置多個,以最常見的DispatcherServlet為例(Spring MVC),這個servlet實際上是一個標準的前端控制器,用以轉發、匹配、處理每個servlet請求。DispatcherServlet上下文在初始化的時候會建立自己的IoC上下文容器,用以持有spring mvc相關的bean,這個servlet自己持有的上下文默認實現類也是XmlWebApplicationContext。在建立DispatcherServlet自己的IoC上下文時,會利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先從ServletContext中獲取之前的根上下文(即WebApplicationContext)作為自己上下文的parent上下文(即第2步中初始化的XmlWebApplicationContext作為自己的父容器)。有了這個parent上下文之后,再初始化自己持有的上下文(這個DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化處理器映射、視圖解析等)。初始化完畢后,spring以與servlet的名字相關(此處不是簡單的以servlet名為Key,而是通過一些轉換)的屬性為屬性Key,也將其存到ServletContext中,以便后續使用。這樣每個servlet就持有自己的上下文,即擁有自己獨立的bean空間,同時各個servlet共享相同的bean,即根上下文定義的那些bean。

Bean加載過程

Spring的高明之處在于,它使用眾多接口描繪出了所有裝置的藍圖,構建好Spring的骨架,繼而通過繼承體系層層推演,不斷豐富,最終讓Spring成為有血有肉的完整的框架。所以查看Spring框架的源碼時,有兩條清晰可見的脈絡:

1)接口層描述了容器的重要組件及組件間的協作關系;

2)繼承體系逐步實現組件的各項功能。

接口層清晰地勾勒出Spring框架的高層功能,框架脈絡呼之欲出。有了接口層抽象的描述后,不但Spring自己可以提供具體的實現,任何第三方組織也可以提供不同實現, 可以說Spring完善的接口層使框架的擴展性得到了很好的保證。縱向繼承體系的逐步擴展,分步驟地實現框架的功能,這種實現方案保證了框架功能不會堆積在某些類的身上,造成過重的代碼邏輯負載,框架的復雜度被完美地分解開了。

Spring組件按其所承擔的角色可以劃分為兩類:

1)物料組件:Resource、BeanDefinition、PropertyEditor以及最終的Bean等,它們是加工流程中被加工、被消費的組件,就像流水線上被加工的物料;

BeanDefinition:Spring通過BeanDefinition將配置文件中的配置信息轉換為容器的內部表示,并將這些BeanDefinition注冊到BeanDefinitionRegistry中。Spring容器的后續操作直接從BeanDefinitionRegistry中讀取配置信息。

2)加工設備組件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等組件像是流水線上不同環節的加工設備,對物料組件進行加工處理。

InstantiationStrategy:負責實例化Bean操作,相當于Java語言中new的功能,并不會參與Bean屬性的配置工作。屬性填充工作留待BeanWrapper完成

BeanWrapper:繼承了PropertyAccessor和PropertyEditorRegistry接口,BeanWrapperImpl內部封裝了兩類組件:(1)被封裝的目標Bean(2)一套用于設置Bean屬性的屬性編輯器;具有三重身份:(1)Bean包裹器(2)屬性訪問器 (3)屬性編輯器注冊表。PropertyAccessor:定義了各種訪問Bean屬性的方法。PropertyEditorRegistry:屬性編輯器的注冊表

該圖描述了Spring容器從加載配置文件到創建出一個完整Bean的作業流程:

1、ResourceLoader從存儲介質中加載Spring配置信息,并使用Resource表示這個配置文件的資源;

2、BeanDefinitionReader讀取Resource所指向的配置文件資源,然后解析配置文件。配置文件中每一個解析成一個BeanDefinition對象,并保存到BeanDefinitionRegistry中;

3、容器掃描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射機制自動識別出Bean工廠后處理后器(實現BeanFactoryPostProcessor接口)的Bean,然后調用這些Bean工廠后處理器對BeanDefinitionRegistry中的BeanDefinition進行加工處理。主要完成以下兩項工作:

1)對使用到占位符的元素標簽進行解析,得到最終的配置值,這意味對一些半成品式的BeanDefinition對象進行加工處理并得到成品的BeanDefinition對象;

2)對BeanDefinitionRegistry中的BeanDefinition進行掃描,通過Java反射機制找出所有屬性編輯器的Bean(實現java.beans.PropertyEditor接口的Bean),并自動將它們注冊到Spring容器的屬性編輯器注冊表中(PropertyEditorRegistry);

4.Spring容器從BeanDefinitionRegistry中取出加工后的BeanDefinition,并調用InstantiationStrategy著手進行Bean實例化的工作;

5.在實例化Bean時,Spring容器使用BeanWrapper對Bean進行封裝,BeanWrapper提供了很多以Java反射機制操作Bean的方法,它將結合該Bean的BeanDefinition以及容器中屬性編輯器,完成Bean屬性的設置工作;

6.利用容器中注冊的Bean后處理器(實現BeanPostProcessor接口的Bean)對已經完成屬性設置工作的Bean進行后續加工,直接裝配出一個準備就緒的Bean。

總結

Spring IOC容器主要有繼承體系底層的BeanFactory、高層的ApplicationContext和WebApplicationContext

Bean有自己的生命周期

容器啟動原理:Spring應用的IOC容器通過tomcat的Servlet或Listener監聽啟動加載;Spring MVC的容器由DispatchServlet作為入口加載;Spring容器是Spring MVC容器的父容器

容器加載Bean原理:

BeanDefinitionReader讀取Resource所指向的配置文件資源,然后解析配置文件。配置文件中每一個解析成一個BeanDefinition對象,并保存到BeanDefinitionRegistry中;

容器掃描BeanDefinitionRegistry中的BeanDefinition;調用InstantiationStrategy進行Bean實例化的工作;使用BeanWrapper完成Bean屬性的設置工作;

單例Bean緩存池:Spring 在 DefaultSingletonBeanRegistry 類中提供了一個用于緩存單實例 Bean 的緩存器,它是一個用 HashMap 實現的緩存器,單實例的 Bean 以 beanName 為鍵保存在這個HashMap 中。

1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加群。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加群。

3、如果沒有工作經驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加群。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加群。

5. Java架構進階群號:668395460

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

推薦閱讀更多精彩內容