深入浅出 MVC 以及SpringMVC源码分析

第一章 深入浅出 MVC

写在最前:

本章课程介绍的是 MVC 模型,它包含了 Model(模型),View(视图)和 Controller(控制器)。
其中 Model,通常指的就是 JavaBean。
View,通常指的是 JSP 或者 HTML(即用于展示数据的资源,包括静态资源和动态资源)。
Controller,通常指的是 Servlet 或者 Filter,以及框架中封装的各类控制器。
首先,解释一下 JavaBean,请看下图:


JavaBean

其次,MVC 模型,它是针对于表现层的设计模型。
最后,本课程内容会涉及常见框架 MVC 框架 SpringMVC 和 Struts2 的源码剖析,思路分析以及部分功能的代码实现。要求学习者有一定的前置知识,例如 SpringMVC 的使用,Struts2 的使用等等

1.1 表现层模型 MVC 的由来

1.1.1 Model1 模型
Model1 模型是很早以前项目开发的一种常见模型,只有 jsp 和 JavaBean 两部分组成。
它的优点是:结构简单。开发小型项目时效率高。
它的缺点也同样明显:
第一:JSP 的职责兼顾于展示数据和处理数据(也就是干了控制器和视图的事)
第二:所有逻辑代码都是写在 JSP 中的,导致代码重用性很低。
第三:由于展示数据的代码和部分的业务代码交织在一起,维护非常不便。
所以,结论是此种设计模型已经被淘汰没人使用了。
下图展示 Model1 模型的执行过程:

Model1

1.1.2 Model2 模型
Model2 模型是在 Model1 的基础上进行改良,它是 MVC 的模型的一个经典应用。它把处理请求和展示数据进
行分离,让每个部分各司其职。此时的 JSP 已经就是纯粹的展示数据了,而处理请求的事情交由控制器来完成,使
每个组件充分独立,提高了代码可重用性和易维护性。下图展示的就是 Model2 模型:

Model2

1.2 MVC 模型的优略分析

1.2.1 MVC 模型的优势
第一:清晰的职责划分。
第二:每个组件作用独立。有利于代码的重用。
第三:由于可重用性强,所以后期维护起来方便。
第四:任何项目都适用(现在没必要照本宣科的说什么适用于大型项目,其实中小型项目的表现层也可以采用
此种模型来开发,并且更易于后期的扩展,即二次开发)

1.2.2 MVC 模型的弊端
任何事情都是有其两面性,MVC 模型也并不是全方位优秀的设计模型。它的弊端体现在:
第一:展示数据响应速度慢(这里讨论的是 JSP。因为 jsp 要经过翻译成 java,编译成 class,然后展示)
第二:对开发人员的要求高,需要合理的设计和严谨的架构。
第三:异步交互并不方便(因为响应回 ajax 引擎之后的数据处理,需要有 dom 编程的功底)

1.2.3 基于异步请求的 MVVM 模式
它全称是 Model View VielModel。是针对 mvc 模型的再次改良,不过只改良了展示数据的部分。
(Controller 的再次优化交给了框架,Model 部分已经无需再优化)
在上一小节,我们提出了异步交互不变的弊端,这主要是在异步展示数据时,javascript 的逻辑处理和数据
显示交织在了一起,当我们想进行调整时,需要阅读大量的代码,给后期维护造成了影响。而 MVVM 它把 javascript
的逻辑处理和数据展示分开,可以让使用者在后期维护时,针对不同的需求进行调整。例如:如果是逻辑部分需要
处理,则修改逻辑部分代码。如果是数据显示位置需要调整,则修改展示部分的代码,使前端展示更加灵活,也更加合理。

MVVM

第2章 基于 MVC 模型框架之:SpringMVC

2.1 SpringMVC 的源码 分析

2.1.1 SpringMVC 的执行过程分析
首先,我们先来看一下 springmvc 官方文档中提供的执行过程图。

SpringMVC执行流程

通过此图,我们可以看到其实都是由前端控制器负责找到要执行的控制器方法。这个前端控制器就是SpringMVC 的核心控制器 DispatcherServlet。接下来,我们通过一个示例来看一下 SpringMVC 请求的全流程

//pom.xml依赖
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.3</version>
    </dependency>
</dependencies>
package cn.icanci.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Author: icanci
 * @ProjectName: mvc
 * @PackageName: cn.icanci.controller
 * @Date: Created in 2020/2/19 8:30
 * @ClassAction: 控制器
 */
@Controller
@RequestMapping("/controllerDemo")
public class ControllerDemo1 {

    @RequestMapping("/sayHello")
    public String sayHello(){
        System.out.println("ControllerDemo1.sayHello");
        return "success";
    }
}
springmvc执行过程分析

在执行的时候,首先加载注解和对应的类和方法,然后再去使用 为什么在页面还没有加载的时候就已经创建好了,是为了避免反复解析 映射解析一次就够了,如下图


解析

DispatcherServlet 的 继承体系图如下

DispatcherServlet的继承体系

2.1.2 SpringMVC 中三大组件详解

2.1.2.1 处理器映射器

它指的是:RequestMappingHandlerMapping
是在 Spring 的 3.1 版本之后加入的。它的出现,可以让使用者更加轻松的去配置 SpringMVC 的请求路径映
射。去掉了早期繁琐的 xml 的配置(关于这个内容,请见 2.1.8 小节)。
它的配置有两种方式:都是在 springmvc.xml 中加入配置。

第一种方式:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

第二种方式:

<mvc:annotation-driven></mvc:annotation-driven>

在这两种方式中,第二种方式更加的完善,它可以帮我们在容器中添加很多的 bean(更多说明请看 2.1.9 小节)。
它 起 的 作 用 是 为 我 们 建 立 起 @RequestMapping 注 解 和 控 制 器 方 法 的 对 应 关 系 。 并 且 存 在 于
MappingRegistry 对象中的 mappingLookup 的 Map 中,该 Map 是个 LinkedHashMap。对应关系的建立时机
是在应用加载的时候,也就是当服务器启动完成后,这些对应关系已经建立完成了。从而做到在我们访问的时候,
只是从 Map 中获取对应的类和方法的信息,并调用执行

2.1.2.2 处理器适配器

要清晰的认识 SpringMVC 的处理器适配器,就先必须知道适配器以及它的作用。我们先通过下图,直观的了解一下:

接口

通过上面三张图,我们可以直观的感受到,它是把不同的接口都转换成了 USB 接口。

带入到我们 SpringMVC 中,就是把不同的控制器,最终都可以看成是适配器类型,从而执行适配器中定义的方法。更深层次的是,我们可以把公共的功能都定义在适配器中,从而减少每种控制器中都有的重复性代码。就如上图中所显示的,像公共数据交换都可以在 USB 接口中定义,而无需在三种不同接口中重复定义。

我们通过 2.1.1 小节,学习了 SpringMVC 的执行过程,最终调用的是前端控制器DispatcherServlet 的 doDispatch 方法,而该方法中的 HandlerAdapter 的 handle 方法实际调用了我们自己写的控制器方法。而我们写的控制方法名称各不一样,它是通过 handle 方法反射调用的。但是我们不知道的是,其实 SpringMVC 中处理器适配器也有多个。

第一个

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter

使用此适配器,适用的控制器写法:要求实现 Controller 接口

//配置SpringMVC的配置文件
<!-- 基于Controller接口配置控制器的方式 -->
<bean id="simpleControllerHandlerAdapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<bean name="/sayHell02" class="cn.icanci.controller.ControllerDemo2"></bean>
package cn.icanci.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

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

/**
 * @Author: icanci
 * @ProjectName: mvc
 * @PackageName: cn.icanci.controller
 * @Date: Created in 2020/2/19 13:30
 * @ClassAction:
 */
public class ControllerDemo2 implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("success2");
        return mv;
    }
}

第二个

org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter

使用此适配器的控制器写法:要求实现 HttpRequestHandler 接口

配置SpringMVC.xml配置文件

<!-- 基于实现 HttpRequestHandler 接口的控制器方式 -->
<bean name="/sayHello3" class="cn.icanci.controller.ControllerDemo3"></bean>
<bean id="HttpRequestHandler" class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>
package cn.icanci.controller;

import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: icanci
 * @ProjectName: mvc
 * @PackageName: cn.icanci.controller
 * @Date: Created in 2020/2/19 13:52
 * @ClassAction:
 */
public class ControllerDemo3 implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getRequestDispatcher("/WEB-INF/pages/success3.jsp").forward(request,response);
        return ;
    }
}

第三个

org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

这种方式也是我们实际开发中采用最多的。它的要求是我们用注解@Controller 配置控制器

修改SpringMVC.xml的配置文件

<bean id="requestMappingHandlerAdapter"
          class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
</bean>
//但是一般都是配置注解支持 如下
<mvc:annotation-driven></mvc:annotation-driven>
package cn.icanci.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Author: icanci
 * @ProjectName: mvc
 * @PackageName: cn.icanci.controller
 * @Date: Created in 2020/2/19 8:30
 * @ClassAction: 控制器
 */
@Controller
@RequestMapping("/controllerDemo")
public class ControllerDemo1 {

    @RequestMapping("/sayHello")
    public String sayHello(){
        System.out.println("ControllerDemo1.sayHello");
        return "success";
    }
}
2.1.2.3 视图解析器

首先,我们得先了解一下 SpringMVC 中的视图。视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。

为了实现视图模型和具体实现技术的解耦,Spring 在org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口。

我们的视图是无状态的,所以他们不会有线程安全的问题。无状态是指对于每一个请求,都会创建一个 View对象。

在 SpringMVC 中常用的视图类型:


SpringMVC 中常用的视图类型

接下来就是了解视图解析器的作用。View Resolver 负责将处理结果生成 View 图,View Resolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。视图对象是由视图解析器负责实例化。视图解析器的作用是将逻辑视图转为物理视图,所有的视图解析器都必须实现 ViewResolver 接口。

SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。

程序员可以选择一种视图解析器或混用多种视图解析器。可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高,SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出ServletException异常。


视图解析器说明
2.1.3 不需要 视图解析器的场景分析

在分析之前,我们先需要回顾下控制器方法的返回值,此处我们都是以注解

@Controller 配置控制器为例,控制器的方法返回值其实支持三种方式:

第一种:String 类型。借助视图解析器,可以在指定位置为我们找到指定扩展名的视图。视图可以是 JSP,HTML 或者其他的控制器方法上的 RequestMapping 映射地址。前往指定视图的方式,默认是请求转发,可以通过redirect:前缀控制其使用重定向。

第二种:void,即没有返回值。因为我们在控制器方法的参数中可以直接使用原始 SerlvetAPI 对象 HttpServletRequest 和 HttpServletResponse 对象,所以无论是转发还是重定向都可以轻松实现,而无需使用返回值。

第三种:ModelAndView 类型。其实我们跟踪源码可以发现在 DispatcherServlet 中的 doDispatch 方法执行时,HandlerAdapter 的 handle 方法的返回值就是ModelAndView,只有我们的控制器方法定义为 void时,才不会返回此类型。当返回值是 String 的时候也会创建 ModelAndView 并返回。

通过上面三种控制器方法返回值,我们可以再深入的剖析一下我们请求之后接收响应的方式,其实无外乎就三种。

第一种: 请求转发
第二种: 重定向
第三种: 直接使用 Response 对象获取流对象输入。可以是字节流也可以是字符流。

接下就分析,这三种方式的本质区别。
其中请求转发和重定向的区别相信大家已经很熟悉了。但是它们的共同点呢?就是都会引发页面的跳转。

在我们的实际开发中,如果我们不需要页面跳转,即基于 ajax 的异步请求,用 json 数据交互时,即可不配置任何视图解析器。前后端交互是通过 json 数据的,利用@RequestBody 和@ResponseBody 实现数据到 java对象的绑定(当然还要借助 Jackson 开源框架)。

2.1.4 请求参数封装的实现原理

在使用 SpringMVC 实现请求参数封装时,它支持基本类型,POJO 类型和集合类型。其封装原理其实就是使用我们原始的 ServletAPI 中的方法,并且配合反射实现的封装。此处以最简单的 String 和 Integer 两个方法为例,把整个执行过程走一圈。

先来看控制器的方法:

package cn.icanci.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;

/**
 * @Author: icanci
 * @ProjectName: mvc
 * @PackageName: cn.icanci.controller
 * @Date: Created in 2020/2/19 17:56
 * @ClassAction: 接收浏览器的异步请求 并返回json数据
 */
@Controller
@RequestMapping("/a")
public class controllerDemo4 {

    @RequestMapping("/b")
    public void sayHello(HttpServletResponse resp) throws Exception{
        System.out.println("controllerDemo4.sayHello");
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("{\"id\":1,\"username\":\"haxi\"}");
    }
}
2.1.5 常用注解的 使用场景及 实现思路分析:
2.1.5.1 RequestParam

首先我们要明确,我们的请求参数体现形式是什么样的。

在请求体的 MIME 类型为 application/x-www-form-urlencoded 或者 application/json 的情况下,无论 get/post/put/delete 请求方式,参数的体现形式都是 key=value。
再来,通过上一小节我们知道,SpringMVC 是使用我们控制器方法的形参作为参数名称,再使用 request 的getParameterValues 方法获取的参数。所以才会有请求参数的 key 必须和方法形参变量名称保持一致的要求。

但是如果形参变量名称和请求参数的 key 不一致呢?此时,参数将无法封装成功。此时 RequestParam 注解就会起到作用,它会把该注解 value 属性的值作为请求参的 key 来获取请求参数的值,并传递给控制器方法。

@Controller
public class ControllerTest5 {
    @RequestMapping()
    public String testParam(@RequestParam("username")String name,Integer age){
        System.out.println(name+" "+age);
        return "seccess";
    }
}
2.1.5.2 RequestBody

在 2.1.4 小节,我们通过源码分析得知,SpringMVC 在封装请求参数的时候,默认只会获取参数的值,而不会把参数名称一同获取出来,这在我们使用表单提交的时候没有任何问题。因为我们的表单提交,请求参数是key=value 的。但是当我们使用 ajax 进行提交时,请求参数可能是 json 格式的:{key:value},在此种情况下,要想实现封装以我们前面的内容是无法实现的。此时需要我们使用@RequestBody 注解


<script src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
<script type="text/javascript">
    $(function () {
        $("#ajaxBtn").click(function () {
            $.ajax({
                type: "POST",
                url: "${pageContext.request.contextPath}/hello2",
                dataType: "text",
                data: "{'name':'test','age':18}",
                contentType: "application/json",
                success: function (data) {
                    alert(data);
                }
            });
        });
    })
</script>
@Controller
public class ControllerDemo6 {
    @RequestMapping("c")
    public void testParm(@RequestBody String body){
        System.out.println(body);
    }
}

先获取的请求参数 MIME 类型 MediaType,然后再把整个内容获取出来,并传递给我们的控制器方法。
需要注意的是,此注解并不能为我们提供封装到 pojo 的操作,它只能把请求体中全部内容获取出来,仅此而已,要想实现封装,需要借助 jackson 开源框架。

2.1.5.3 PathVariable

它是 SpringMVC 在 3.0 之后新加入的一个注解,是 SpringMVC 支持 Restful 风格 URL 的一个重要标志

SpringMVC在实现请求URL使用占位符传参并封装到控制器方法的形参中,是通过请求域来实现的。最后把请求域转成一个 Map,再根据形参的名称作为 key,从 map 中获取 value,并给形参赋值。当然如果我们使用了 PathVariable 注解的 value 属性,则不会以形参名称为 key,而是直接使用 value属性的值作为 key 了

2.1.6 拦截器的 AOP 思想

AOP 思想是 Spring 框架的两大核心之一,是解决方法调用依赖以及提高方便后期代码维护的重要思想。它是把代码中高度重复的部分抽取出来,并在适当的时机,通过代理机制来执行,从而做到不修改源码对已经写好的方法进行增强。而拦截器正式这种思想的具体实现

2.1.7 自定义拦截器中三个方法说明 及使用场景
2.1.7.1 preHandle

此方法的执行时机是在控制器方法执行之前,所以我们通常是使用此方法对请求部分进行增强。同时由于结果视图还没有创建生成,所以此时我们可以指定响应的视图。

2.1.7.2 postHandle

此方法的执行时机是在控制器方法执行之后,结果视图创建生成之前。所以通常是使用此方法对响应部分进行
增强。因为结果视图没有生成,所以我们此时仍然可以控制响应结果。

2.1.7.3 afterCompletion

此方法的执行时机是在结果视图创建生成之后,展示到浏览器之前。所以此方法执行时,本次请求要准备的数据具已生成完毕,且结果视图也已创建完成,所以我们可以利用此方法进行清理操作。同时,我们也无法控制响应结果集内容。

2.1.8 为什么不使用 XML 配置 SpringMVC

先来看基于 XML 的 SpringMVC 配置:
第一步:配置 web.xml
第二步:编写控制器
第三步:编写 springmvc.xml
第四步:配置控制器
第五步:配置处理器映射器,处理器适配器。
第六步:配置视图解析器。
其中,前 3 步和第六步基于注解配置时也都有,而第四第五步注解配置只需:

<!-- 开启 springmvc 对注解的支持-->
<mvc:annotation-driven></mvc:annotation-driven>

而 XML 配置则需:

<!-- 实现 Controller 接口-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<bean name="/sayhello2" class="com.itheima.web.controller.HelloController2"/>
<!-- 继承 HttpRequestHandler 类-->
<bean name="/sayhello3" class="com.itheima.web.controller.HelloController3"/>
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>

而对比注解配置只需一个 Controller 注解和一个 RequestMapping 注解来比,显然注解来的更方便。

2.1.9 mvc:annotation-driven 的说明

它就相当于在 xml 中配置了:

<!-- Begin -->
<!-- HandlerMapping -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean><bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- HadnlerExceptionResolvers -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"></bean>
<!-- End -->
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容