SpringMVC從入門到放棄之第二章SpringMVC入門

額(⊙o⊙)…
繼續記筆記。。。看看
SpringMVC從入門到放棄之第一章Web MVC簡介

一個在實驗室的時候就是這樣

原文地址

2.1、Spring Web MVC是什么

Spring Web MVC是一種基于Java的實現了Web MVC設計模式的請求驅動類型的輕量級Web框架,即使用了MVC架構模式的思想,將Web進行職責解耦,基于請求驅動指的就是使用請求—響應模型,框架的目的就是幫助我們簡化開發,Spring Web MVC也是要簡化我們日常Web開發的。
Spring Web MVC也是服務到工作者模式的體現,但進行可優化。前端控制器是DispatcherServlet;應用控制器其實拆為處理器映射器(Handler Mapping)進行處理器管理和視圖解析器(View Resolver)進行視圖管理;頁面控制器/動作/處理器為Controller接口(僅包含ModelAndView handleRequest(request,response)方法)的實現(也可以是任何POJO類);支持本地化(Locale)解析、主題(Theme)解析及文件上傳等;提供了非常靈活的數據驗證、格式化和數據綁定機制;提供了強大的約定大于配置(慣例優先原則)的契約式編程支持。

2.2、Spring Web MVC能幫助我們做什么

1、讓我們能非常簡單的設計出干凈的Web層和薄薄的Web層;
2、進行更簡潔的Web層開發;
3、天生與Spring框架集成(如IOC容器、AOP等);
4、提供強大的約定大于配置的契約式編程支持;
5、能簡單的進行Web層的單元測試;
6、支持靈活的URL到頁面控制器的映射;
7、非常容易與其他視圖技術集成;
8、非常靈活的數據驗證、格式化和數據綁定機制,能使用任何對象進行數據綁定,不必實現特定框架的API;
9、提供一套強大的JSP標簽庫,簡化JSP開發;
10、支持靈活的本地化、主題等解析;
11、更加簡單的異常處理;
12、對靜態資源的支持;
13、支持Restful風格;

2.3、Spring Web MVC架構

Spring Web MVC框架也是一個基于請求驅動的Web框架,并且也是用了前端控制器模式來進行設計,再根據請求映射規則分發給相應的頁面控制器(動作/處理器)進行處理。首先讓我們來整體看一下Spring Web MVC請求的處理流程。

2.3.1、Spring Web MVC處理請求過程

Spring Web MVC請求處理過程

具體執行步驟如下:
1、首先客戶發送請求——>前端控制器,前端控制器根據請求信息(如URL)來決定選擇哪一個頁面控制器進行處理并把請求委托給它,即以前控制器的控制邏輯部分;如圖中的1、2步驟;
2、頁面控制器接收到請求后,進行功能處理,首先需要收集和綁定請求參數到一個對象,這個對象在Spring Web MVC中叫命令對象,并進行驗證,然后將命令對象委托給業務對象進行處理;處理完畢后返回一個ModelAndView(模型數據和邏輯視圖名);如圖中的3、4、5步驟;
3、前端控制器收回控制權,然后根據返回的邏輯視圖名,選擇相應的視圖進行渲染,并把數據模型傳入以便視圖渲染;如圖中的6、7步驟;
4、前端控制器再次收回控制權,將響應返回給用戶;如圖中的8步驟;至此整個響應到此結束。
問題:
1、請求如何給前端控制器?
2、前端控制器如何根據請求信息選擇頁面控制器進行功能處理?
3、如何支持多種頁面控制器呢?
4、頁面控制器如何使用業務對象?
5、頁面控制器如何返回模型數據?
6、前端控制器如何根據頁面控制器返回的邏輯視圖名選擇具體的視圖進行渲染?
7、不同的視圖技術如何使用相應的模型數據?
首先我們知道有如上問題,那么這些問題我們如何進行解決呢?在后面依次進行回答。

2.3.2、Spring Web MVC架構

1、Spring Web MVC核心架構圖


Spring Web MVC核心架構圖

架構圖對應的DispatcherServlet核心代碼如下:

//前端控制器分派方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception
{
      HttpServletRequest processedRequest = request;
      HandlerExecutionChain mappedHandler = null;
      int interceptorIndex = -1;
      try {
            ModelAndView mv;
            boolean errorView = false;
            try {
            //檢查是否是請求是否是multipart(如文件上傳),如果是將通過MultipartResolver解析
            processedRequest = checkMultipart(request);
            //步驟2、請求到處理器(頁面控制器)的映射,通過HandlerMapping進行映射
            mappedHandler = getHandler(processedRequest, false);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                  noHandlerFound(processedRequest, response);
                  return;
            }
            //步驟3、處理器適配,即將我們的處理器包裝成相應的適配器(從而支持多種類型的處理器)
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // 304 Not Modified緩存支持
            //此處省略具體代碼
            // 執行處理器相關的攔截器的預處理(HandlerInterceptor.preHandle)
            //此處省略具體代碼
            // 步驟4、由適配器執行處理器(調用處理器相應功能處理方法)
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 
            // Do we need view name translation?
            if (mv != null && !mv.hasView()) {
                mv.setViewName(getDefaultViewName(request));
            }
            // 執行處理器相關的攔截器的后處理(HandlerInterceptor.postHandle)
            //此處省略具體代碼
     }
     catch (ModelAndViewDefiningException ex) {
            logger.debug("ModelAndViewDefiningException encountered", ex);
            mv = ex.getModelAndView();
     }  
     catch (Exception ex) {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(processedRequest, response, handler, ex);
            errorView = (mv != null);
     }
            //步驟5 步驟6、解析視圖并進行視圖的渲染
            //步驟5 由ViewResolver解析View(viewResolver.resolveViewName(viewName,         locale))
            //步驟6 視圖在渲染時會把Model傳入(view.render(mv.getModelInternal(), request, response);)
            if (mv != null && !mv.wasCleared()) {
            render(mv, processedRequest, response);
                if (errorView) {
                     WebUtils.clearErrorRequestAttributes(request);
                }
        } else {
            if (logger.isDebugEnabled()) {
               logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +getServletName() +"': assuming HandlerAdapter completed request handling");
            }
        }
        // 執行處理器相關的攔截器的完成后處理(HandlerInterceptor.afterCompletion)
        //此處省略具體代碼
        catch (Exception ex) {
        // Trigger after-completion for thrown exception.
        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
        }
        catch (Error err) {
        ServletException ex = new NestedServletException("Handler processing failed", err);
        // Trigger after-completion for thrown exception.
        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
        throw ex;
        }
        finally {
        // Clean up any resources used by a multipart request.
            if (processedRequest != request) {
                  cleanupMultipart(processedRequest);
            }
       }
}

核心架構的具體步驟流程如下:
1、首先用戶發送請求———>DispatcherServlet,前端控制器收到請求后自己不進行處理,而是委托給其他的解析器進行處理,作為統一的訪問點,進行全局的流程控制;
2、DispatcherServlet———>HandlerMapping,HandlerMapping將會把請求映射為HandlerExecutionChain對象(包含一個Handler處理器(頁面控制器)對象、多個HandlerInterceptor攔截器)對象,通過這種策略模式,很容易添加新的映射策略;
3、DispatcherServlet———>HandlerAdapter,HandlerAdapter將會把處理器包裝為適配器,從而支持多種類型的處理器,即適配器設計模式的應用,從而很容易支持很多類型的處理器;
4、HandlerAdapter———>處理器功能處理方法的調用,HandlerAdapter將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理;并返回一個ModelAndView對象(包含模型數據、邏輯視圖名);
5、ModelAndView的邏輯視圖名———>ViewRsolver,ViewResolver將把邏輯視圖名解析為具體的View,通過這種策略模式,很容易更換其他視圖技術;
6、View———>渲染,View會根據傳進來的Model模型數據進行渲染,此處的Model實際是一個Map數據結構,因此很容易支持其他視圖技術;
7、返回控制權給Dispatcher,由DispatcherServlet返回響應給用戶,到此一個流程結束。
到此,再來看我們前邊提出的問題:
1、請求如何給前端控制器?這個應該在web.xml中進行部署描述,在HelloWorld中詳細講解。
2、前端控制器如何根據請求控制信息選擇頁面控制器進行功能處理?我們需要配置HandlerMapping進行映射
3、如何支持多種頁面控制器呢?配置HandlerAdapter從而支持多種類型的頁面控制器
4、頁面控制器如何使用業務對象?可以預料到,肯定利用SpringIoc容器的依賴注入功能
5、頁面控制器如何返回模型數據?使用ModelAndView返回
6、前端控制器如何根據頁面控制器返回的邏輯視圖名選擇具體的視圖進行渲染?使用ViewResolver進行解析
7、不同的視圖技術如何使用相應的模型數據?因為Model是一個Map數據結構,很容易支持其他視圖技術
在此我們看出具體的核心開發步驟:
1、DispatcherServlet在web.xml中的部署描述,從而攔截請求到Spring Web MVC
2、HandlerMapping的配置,從而將請求映射到處理器
3、HandlerAdapter的配置,從而支持多種類型的處理器
4、ViewResolver的配置,從而將邏輯視圖名解析為具體視圖技術
5、處理器(頁面控制器)的配置,從而進行功能處理

2.4Spring Web MVC優勢

1、清晰的角色劃分:前端控制器(DispatcherServlet)、 請求到處理器映射(HandlerMapping)、處理器適配器(HandlerAdapter)、視圖解析器(ViewResolver)、處理器或頁面控制器(Controller)、驗證器(Validator)、命令對象(Command請求參數綁定到的對象就叫命令對象)、表單對象(From Object提供給表單展示和提交到的對象就叫表單對象);
2、分工明確,而且擴展點相當靈活,可以很容易擴展,雖然幾乎不需要;
3、由于命令對象就是一個POJO,無需繼承框架特定API,可以使用命令對象直接作為業務對象;
4、和Spring其他框架無縫集成,是其他Web框架所不具備的;
5、可適配,通過HandlerAdapter可以支持任意類作為處理器;
6、可定制性,HandlerMapping、ViewResolver等能夠非常簡單的定制;
7、功能強大的數據驗證、格式化、綁定機制;
8、利用Spring提供的Mock對象能夠非常簡單的進行Web層單元測試;
9、本地化、主題的解析的支持,使我們更容易進行國際化和主題的切換;
10、強大的JSP標簽庫,是JSP編寫更加容易。

2.5、HelloWorld入門

2.5.1、準備開發環境和運行環境

☆開發工具:eclipse
☆運行環境:tomcat7
☆工程:動態Web工程
spring所有框架下載
spring-framework-4.3.1.RELEASE-dist.zip
☆依賴jar包:
1、Spring依賴的jar包:
為了簡單,將spring-framework-4.3.1.RELEASE-dist.zip/dist/下所有的jar包拷貝到項目的WEB-INF/lib目錄下;
2、Spring框架依賴的jar包:
需要添加Apache commons logging日志,此處用的commons-logging-1.2.jar
需要添加jstl標簽庫支持,此處使用的是jstl-1.1.2.jar和standard-1.1.2.jar
3、項目目錄

項目結構圖

4、效果
效果

2.5.2、前端控制器的配置

我們在web.xml中添加如下配置

  <servlet>
    <servlet-name>helloworld</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>helloworld</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

load-on-startup:表示啟動容器時初始化該Servlet;
url-pattern:表示哪些請求交給Spring Web MVC處理,“/”是用來定義默認Servlet映射的。也可以如“*.html”表示攔截所有以html為擴展名的請求。
自此請求已交給Spring Web MVC框架處理,因此我們需要配置Spring的配置文件,默認的DispatcherServlet會加載WEB-INF/[DispatcherServlet的Servlet名字]-servlet.xml配置文件。本例為WEB-INF/helloworld-servlet.xml。

2.5.3、在Spring配置文件中配置HandlerMapping、HandlerAdapter

具體配置在WEB-INF/helloworld.xml文件中:

    <!-- HandlerMapping -->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    
    <!-- HandlerAdapter -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

BeanNameUrlHandlerMapping:表示將在請求的URL和Bean名字映射,如URL為“上下文/hello”,則Spring配置文件必須有一個名字為“/hello”的bean,上下文默認忽略。
SimpleControllerHandlerAdapter:表示所有實現了org.springframework.web.servlet.mvc.Controller接口的Bean可以作為Spring Web MVC中的處理器。如果需要其他類型的處理器可以通過實現HandlerAdapter來解決。

2.5.4、在Spring配置文件中配置ViewResolver

具體配置在WEB-INF/ helloworld-servlet.xml文件中:

    <!-- ViewResolver -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

InternalResourceViewResolver:用于支持Servlet、JSP視圖解析;
viewClass:JstlView表示JSP模板頁面需要使用JSTL標簽庫,classpath中必須啊包含jstl的相關jar包;
prefix和suffix:查找視圖頁面的前綴和后綴(前綴[邏輯視圖名]后綴),比如傳過來的邏輯視圖名為hello,則該jsp視圖頁面應該存放在“/WEB-INF/jsp/hello.jsp”;

2.5.5、開發處理器/頁面控制器

package com.helloworld;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class HelloWorldController implements Controller{

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //1、收集參數
        //2、綁定參數到命令對象
        //3、將命令對象傳如業務對象進行業務處理
        //4、選擇下一個頁面
        ModelAndView mv = new ModelAndView();
        mv.addObject("message", "HelloWorld");
        mv.setViewName("hello");
        return mv;
    }
}

org.springframework.web.servlet.mvc.Controller:頁面控制器/處理器必須是實現Controller接口,注意別選錯了;后邊我們會學習其他的處理器實現方式;
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp):功能處理方法,實現相應的功能處理,比如收集參數、驗證參數、綁定參數到命令對象、將命令對象傳入到業務對象進行業務處理,最后返回ModelAndView對象;
ModelAndView:包含了視圖要實現的模型數據和邏輯視圖名;“mv.addObject("message", "Hello World!");”表示添加模型數據,此處可以是任意的POJO對象;“mv.setViewName("hello");”表示設置邏輯視圖名為“hello”,視圖解析器會將其解析為具體的視圖,如前邊的視圖解析器InternalResourceVi。wResolver 會將其解析為“WEB-INF/jsp/hello.jsp”。
我們需要將其添加到Spring配置文件(WEB-INF/helloworld-servlet.xml),讓其接受Spring IoC容器管理:

    <!-- 處理器 -->
    <bean name="/hello" class="com.helloworld.HelloWorldController"/>

name="/hello":前邊配置的BeanNameUrlHandlerMapping,表示如果請求的URL為“上下文/hello”,則將會交給該Bean處理。

2.5.6、開發視圖界面

創建 /WEB-INF/jsp/hello.jsp視圖頁面:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Hello World</title>
</head>
<body>
    ${message }
</body>
</html>

${message}:表示顯示由HelloWorldController處理器傳過來的模型數據。

2.5.7、啟動服務器運行測試

通過請求:http://localhost:8080/HelloWorld/hello如果頁面輸出“Hello World! ”就表明我們成功了!
localhost:服務器地址;
8080:端口;
HelloWorld:web應用上下文;
/hello:映射到控制器部分

2.5.8、運行流程分析

運行流程分析

運行步驟:
1、首先用戶發送請求http://localhost:8080/HelloWorld/hello——>web容器,web容器根據“/hello”路徑映射到DispatcherServlet(url-pattern為/)進行處理;
2、DispatcherServlet——>BeanNameUrlHandlerMapping進行請求到處理的映射,BeanNameUrlHandlerMapping將“/hello”路徑直接映射到名字為“/hello”的Bean進行處理,即HelloWorldController,BeanNameUrlHandlerMapping
將其包裝為HandlerExecutionChain(只包括HelloWorldController 處理器,沒有攔截器);
3、DispatcherServlet——>SimpleControllerHandlerAdapter,SimpleControllerHandlerAdapter 將HandlerExecutionChain中的處理器(HelloWorldController)適配為SimpleControllerHandlerAdapter;
4、SimpleControllerHandlerAdapter——> HelloWorldController 處理器功能處理方法的調用,
SimpleControllerHandlerAdapter 將會調用處理器的handleRequest 方法進行功能處理,該處理方法返回一個ModelAndView給DispatcherServlet;
5、hello(ModelAndView 的邏輯視圖名)——>InternalResourceViewResolver, InternalResourceViewResolver 使用JstlView,具體視圖頁面在/WEB-INF/jsp/hello.jsp;
6、JstlView(/WEB-INF/jsp/hello.jsp)——>渲染,將在處理器傳入的模型數據(message=HelloWorld!)在視圖中展示出來;
7、返回控制權給DispatcherServlet,由DispatcherServlet 返回響應給用戶,到此一個流程結束。
到此HelloWorld就完成了,步驟是不是有點多?而且回憶下我們主要進行了如下配置:
1、前端控制器DispatcherServlet;
2、HandlerMapping;
3、HandlerAdapter;
4、ViewResolver;
5、處理器/頁面控制器;
6、視圖
因此,接下來幾章讓我們詳細看看這些配置,先從DispatcherServlet開始吧。

2.6、POST中文亂碼解決方案

Spring Web MVC框架提供了org.springframework.web.filter.CharacterEncodingFilter用于解決POST方式造成的中文亂碼問題,具體配置如下:

<filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
  </filter>
  <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
  </filter-mapping>

以后我們項目及所有頁面的編碼均為UTF-8。

2.7、Spring新特性

一、Spring2.5 之前,我們都是通過實現Controller 接口或其實現來定義我們的處理器類。
二、Spring2.5 引入注解式處理器支持,通過@Controller 和@RequestMapping注解定義我們的處理器類。并且提供了一組強大的注解:
需要通過處理器映射DefaultAnnotationHandlerMapping和處理器適配器AnnotationMethodHandlerAdapter來開啟支持@Controller 和 @RequestMapping 注解的處理器。

注解 作用
@Controller 用于標識是處理器類
@RequestMapping 請求到處理器功能方法的映射規則
@RequestParam 請求參數到處理器功能處理方法的方法參數上的綁定
@ModelAttribute 請求參數到命令對象的綁定
@InitBinder 自定義數據綁定注冊支持,用于將請求參數轉換到命令對象屬性的對應類型
@SessionAttributes 用于聲明session 級別存儲的屬性,放置在處理器類上,通常列出模型屬性(如@ModelAttribute)對應的名稱,則這些屬性會透明的保存到session 中

三、Spring3.0 引入RESTful 架構風格支持(通過@PathVariable注解和一些其他特性支持),且又引入了
更多的注解支持:

注解 作用
@CookieValue cookie 數據到處理器功能處理方法的方法參數上的綁定
@RequestHeader 請求頭(header)數據到處理器功能處理方法的方法參數上的綁定
@RequestBody 請求的body體的綁定(通過HttpMessageConverter 進行類型轉換)
@ResponseBody 處理器功能處理方法的返回值作為響應體(通過HttpMessageConverter進行類型轉換)
@ResponseStatus 定義處理器功能處理方法/異常處理器返回的狀態碼和原因
@ExceptionHandler 注解式聲明異常處理器
@PathVariable 請求URI 中的模板變量部分到處理器功能處理方法的方法參數上的綁定,從而支持RESTful 架構風格的URI

四、Spring3.1 新特性:
對 Servlet 3.0 的全面支持。

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

推薦閱讀更多精彩內容