Java进阶第11课 框架之Struts2全解析通俗易懂-从零开始学Java

1、什么是框架?

框架就是一组程序的集合,本质上是一组jar包的集合,jar包中存有class文件或一些资源文件。框架的诞生是为了体能帮助程序员快速进行项目的开发,提供一些辅助性的、便捷性的开发API,该API中已经对现有的编程语言sdk及一些功能进行了封装,程序员只需要遵循框架的一些约定,即可调用该API快速开发出符合业务需求的功能及程序。

2、Java的三层结构与框架

Java开发中的分层结构为:

表现层,也叫action层/包,负责处理与界面交互的相关操作,主要框架有Struts2,Spring MVC;

业务层,也叫service层/包,负责复杂的业务逻辑计算和判断,主要框架有Spring(继承了事务处理的功能);

持久层,也叫dao层/包,负责将业务逻辑数据进行持久化存储,主要框架有Hibernate,MyBatis等;

我们一般将用于表现层的框架叫做MVC框架。

3、MVC模型

MVC是一种程序设计的思想模型。将整个程序的功能纵向拆分为数据模型Model,视图View,控制器Controller。控制器是连接模型和视图的桥梁,通过控制器来更新模型对应的数据,并将模型的数据展示在视图中。

4、Struts2的发展历史

由于早期的程序开发是基于Servlet+JSP+JavaBean最原始的开发模式,基本上是按部就班地开发,效率比较低下。

后来Apache组织推出了基于MVC模式的轻量级Web应用框架Apache Struts1,该结构将整个程序开发的代码结构进行了合理的划分,提供了一些比较实用的功能,如验证,国际化等。一经推出,迅速流行。

但是经过开发者的实践,发现Struts1存在一些问题,比如线程不安全,灵活性低,ServletAPI耦合性高,页面间传值复杂等。

后来又有一些新的框架,如SpringMVC和OpenSymphony的WebWork, 他们能够避免Struts1中的一些缺点,从而比Struts1更能让开发者接受。

当然作为MVC框架的发起者Apache组织也不甘落后,在WebWork框架的基础之上借鉴了Struts1的优点重新开发了一个新的Struts框架,即Struts2框架,该框架是一个轻量级的Web应用框架,该框架不兼容Struts1框架。

5、Struts2框架的核心功能介绍

6、Struts2框架的使用步骤

1)拷贝struts2下载包中/apps/struts2-blank/WEB-INF/lib目录中的jar包到项目中的WEB-INF/lib目录中,将这些jar包右键->add as Library即可。

struts运行必备jar包介绍:

struts2-core-2.3.4.jar, 核心类库;

xwork-core-2.3.4.jar, Command模式框架,用于是一个容器,用于创建和销毁bean对象;

ognl-3.0.3.jar, 对象图导航语言,用于读写对象的属性;

freemarker-2.3.18.jar, 用于编写UI标签的模板;

commons-logging-1.1.x.jar, 用于支持Log4j 和JDK的日志记录;

commons-fileupload-1.2.2.jar, 文件上传组件;

commons-io-2.0.1.jar, io读写操作(如文件传输)依赖的jar包;

commons-lang-2.5.jar, 对java.lang包的增强;

2)在web.xml中配置Struts2的前端控制器StrutsPreparedAndExecuteFilter:

struts2

struts2

/*

3)拷贝struts.xml文件到项目的资源文件目录中;

4)定义一个类HelloAction,提供一个public String execute(){}的方法(方法名可以修改),在其中返回一个字符串,用于对应逻辑视图的名称,如login。注意定义Action实现类既可以使用POJO即普通的Java对象外,还可以实现

5)在struts.xml文件中对HelloAction类进行配置,以表示将其对象的创建和销毁的管理交给Struts2的xwork容器去管理。

/pages/user/login.jsp

6) 在web根目录/pages/user/下创建一个login.jsp文件。里面可以写一句话:请登录。

7) 部署web项目到tomcat服务器上,通过浏览器访问HelloAction:

http://localhost:8080/webproject/pss/hello.action

图片发自简书App

7、Struts2的执行流程

1)web.xml文件中配置了Struts2的核心过滤器StrutsPrepareAndExecuteFilter,用于拦截所有的url请求。

2)在web项目启动的时候,会执行StrutsPrepareAndExecuteFilter对象中的init(FilterConfig)方法,在该方法中将传入的FilterConfig对象包装为FilterHostConfig对象,创建并调用InitOperations对象中的initDispatcher(HostConfig)方法,在该方法中将HostConfig对象中的ServletContext对象和初始化参数Map对象包装成一个Dispatcher对象,保存在StrutsPrepareAndExecuteFilter对象中,并根据Dispatcher对象分别创建PrepareOperations和ExecuteOperations对象。前者用来做一些初始化和清理操作,如createActionContext, cleanupRequest, cleanupDispatcher等方法;后者有executeAction方法,该方法用于调用dispatcher对象的serviceAction方法,传入HttpServletRequest, HttpServletResponse, 以及ActionMapping对象。

3)浏览器通过url发起对web项目资源的访问。如http://localhost:8080/webproject/pss/hello

4)当Tomcat收到请求后,经过一些包装和处理后,会执行StrutsPrepareAndExecuteFilter对象中的doFilter方法,在该方法中先判断请求的url是否是已排除的url,如果是,则直接调用FilterChain对象的doFilter(req, resp)方法进行放行;如果不是,则先调用PrepareOperations对象中的setEncodingAndLocale方法,该方法最终目的是使用指定编码格式(默认utf-8)对request进行编码(调用request.setCharacterEncoding(encoding)方法), 以及调用response.setLocale(Locale)方法。

然后调用PrepareOperations对象的createActionContext方法,在该方法中先通过ValueStackFactory创建ValueStack对象,调用该对象的getContext()方法拿到map成员变量,然后调用dispatcher对象的createContextMap方法,将返回的Map对象中的key-value值存入ValueStack对象中。

然后通过ValueStack对象中的map创建ActionContext对象,并将该对象保存到ActionContext中的静态变量中。

调用PrepareOperations对象的wrapRequest(HttpServletRequest)方法将HttpServletRequest封装为StrutsRequestWrapper对象,即子类对象。该对象中有一个boolean变量disableRequestAttributeValueStackLookup,用于在getAttribute(String)方法被调用时,决定是否不从ValueStack中取值。默认是false,即允许从ValueStack中取值。

调用PrepareOperations对象的findActionMapping(request,response,forceLookup)方法,从request对象中根据struts.actionMapping这个key拿到ActionMapping对象并返回。如果request中没有该对象或者其为null,则通过dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager())来创建出ActionMapping对象。

如果当前request对应的ActionMapping为null,经过ExecuteOperations对象execute.executeStaticResourceRequest(request, response)确认请求的是静态资源对象(即非Action对象), 则直接调用FilterChain对象的doFilter(request,response) 对当前请求进行放行处理;

否则,调用ExecuteOperations对象的executeAction方法,在该方法中会先根据ActionMapping对象来决定是否要调用struts.xml中注册的某个Action对象,如果是,则创建对应的ActionProxy(代理对象),并将请求的处理交给ActionProxy对象,ActionProxy则创建一个ActionInvocation的实例,由ActionInvocation实例来实现对Action实例中指定的业务方法进行调用。但在调用Action对象的前后会涉及到拦截器栈或拦截器对象的调用。

当Action被调用完毕,ActionInvocation会根据struts.xml中的配置找到对应的返回结果result,可以是JSP或html或其他Action链等。

注意:在整个struts框架中,所有的对象都是由xwork容器负责创建和管理的。

8、Struts2的配置相关的文件及其加载顺序(由先到后):

2)struts-default.xml 在struts2-core-?.jar包中,主要包含了框架依赖对象的配置和结果类型,以及拦截器等配置;

图片发自简书App

3)struts-plugin.xml 文件存在于struts-?-plugin-?.jar包中,主要用于配置struts的框架,由框架提供;

4)struts.xml 文件,就是我们在struts2项目的source folder目录中定义的文件。可以用来配置常量,package和Action,拦截器等;

5)struts.properties 文件,该文件可以由开发者创建并跟struts.xml文件放在同一个目录中,可以用来覆盖default.properties中的常量的定义;

6)web.xml文件,该文件是web项目中WEB-INF目录下的重要配置文件。

注意:由于加载有先后顺序,所以后面文件配置中的常量会覆盖前面加载的文件中同名的常量。

9. struts2中常见常量介绍:

用于指定默认编码集,用在request.setCharacterEncoding(String)方法中,以及freemarker、velocity等。

其中freemarker是模板引擎,是一种基于模板允许其中key的值变动,将其生成输出的文本如html格式的文本的工具)。

图片发自简书App

其中velocity是一个基于java的模板引擎, 允许仅使用简单的模板语言(template language)来引用由java代码定义的对象。

指定请求资源的后缀名,如.action, 以及没有后缀名等。

设置浏览器是否缓存静态内容,默认为true。一般在生产环境下使用,而在开发阶段为了测试,最好关闭。

当struts的配置文件(xml格式)修改后,系统自动重新加载,而不用重启tomcat。默认为false。

struts.devMode = true

设置开发者模式,该模式下修改struts.xml文件后不需要重启服务器即可生效。

设置默认的视图主题样式,simple指定为简化的主题样式。

struts.enable.DynamicMethodInvocation = false

设置是否支持动态方法调用。为true时,就可以在struts.xml配置“*”的通配符,来调用action里的方法。

例如:

/pages/success.jsp

/pages/error.jsp

调用时就可以通过url来传递方法名,即动态方法调用。如:

http://localhost:8080/webproject/pss/login_mymethod

表示系统会调用com.abc.action.LoginAction类的对象中的mymethod方法。该方法返回“success”时,跳转到web根目录下/pages/success.jsp文件;如果返回“error”时,跳转到web根目录/pages/error.jsp文件。

10、struts.xml文件的编写规则

常量的定义:

如果要在里面定义常量,格式为:

包的定义:

如果要定义包,使用

节点来定义。

其中name属性为包名,不同的包,name值应该不同。

extends属性表示当前的包继承自哪个包,一般都继承自struts-default包。继承了struts-default包,则表示当前包拥有了struts-default包所定义的资源,如返回结果类型,拦截器等。struts-default包是在struts-core-?.jar/struts-default.xml文件中定义。

namespace属性表示定义了一个命名空间,一般以”/“开头,如”/pss“。命令空间和节点的name属性组合成了所在包下面的action对象的访问路径。如

则action资源的请求路径为:http://服务器ip:tomcat端口号/项目的上下文路径/包的namespace值/action的name值.action

abstract属性用来将包定义为抽象的包,即只能用来被继承,而里面没有定义任何的节点,也就是没有任何的action对象。

Action实现类的定义:

class属性对应Action实现类的全限定名,默认是ActionSupport类;

method属性指定要调用Action的业务方法;

节点定义在节点中或者中,用于配置业务方法执行结果对应跳转或重定向到的视图路径。

格式为:

name属性用来定义逻辑视图名,对应于action中业务方法执行后返回的字符串,缺省为"success";

type属性用来指定跳转的类型,缺省是dispatcher表示请求转发到jsp文件(页面),redirect表示重定向到jsp文件,chain表示从一个action请求转发到另一个action,redirectAction表示从一个action重定向到另一个action,stream表示返回流对象,用于文件下载;

如:/pages/ok.jsp

注意:如果要访问不同命名空间下的action,例如要访问”/pss“命名空间中的name值为hello的action,则在节点中,添加如下两个节点:

/pss

hello

action的扩展:比如要执行name值为hello的action对象中的abc()业务方法,有三种做法:

1)直接在节点中配置method属性的值为abc;

2)http://服务器ip:端口号/web项目上下文路径/package namespace/hello!abc

此种方式需要配置常量:

3)使用通配符

则请求的url为:http://服务器ip:端口号/web项目上下文路径/package namespace/hello_abc

11、如何在Action中获取request,response,session,cookie等?

首先我们要明白在Servlet中的业务方法service方法中,已经通过参数将HttpServletRequest和HttpServletResponse两个对象传入进来了,我们可以直接用,通过request.getSession()就可以拿到Session对象,通过request.getCookies()就可以拿到所有的Cookie对象。

那么我们现在来看下,在Struts的Action中如何获取上述四个对象呢?有两种方式:

方式一:(有耦合,不推荐)通过实现Struts提供的感知接口来自动获取,如ServletRequestAware,ServletResponseAware,SessionAware等接口。

图片发自简书App

之所以能通过感知接口拿到相关的Servlet对象,是因为在struts-core-?.jar包中的struts-default.xml文件中定义了servletConfig对应的类。

图片发自简书App

我们来看看ServletConfigInterceptor类的代码:

图片发自简书App

方式二:直接通过ServletActionContext工具类中的静态方法来获取相关对象。

图片发自简书App

方式三:(强烈推荐)通过ActionContext对象中的方法来获取。ActionContext对象本身代表着当前Action对象的上下文环境信息,也就是对应着一次请求的相关信息。

图片发自简书App

ActionContext.getContext() 获取到ActionContext的对象,通过该对象调用getParameters()获取到所有参数的Map集合。

ActionContext是线程安全的,里面定义了ThreadLocal actionContext对象,实现了将ActionContext对象与当前线程绑定在一起。

12、JSP页面与Action组件间相互注入数据的几种方式:

首先我们要明白,凡是请求中的参数,可以在Action中直接使用request.getParameter(key)或者request.getParamterValues(key)方式来获取到参数的值或数组值。但是这种做法很麻烦,而且需要在Servlet中做类型转换。有没有更好的做法呢?当然是有,请看下文。

方式一:将Action看做一个Model对象,在Action中直接定义private的属性名,提供对应的setter方法即可。

方式二:(推荐的方式)创建一个含有属性的Model类,在Action中通过对该Model类提供属性及getter和setter方法,页面会通过ognl表达式对数据进行注入。

图片发自简书App

方式三:创建一个含有属性的Model类,让Action实现ModelDriven接口,该接口的的泛型类型为该Model类。在Action中创建一个Model类的对象作为属性,在复写的getModel()方法中,返回当前Action中的Model类的对象。

图片发自简书App

显然方式一如果传递的数据个数比较多时,代码比较臃肿;方式三需要实现ModelDriven接口,有耦合性;方式二显然是最佳的使用方式。但在实际的开发中,经常是方式二和方式一的组合使用。

13、拦截器Interceptor

所谓的拦截器是指能够对指定Action对象的调用进行动态拦截的组件,可以在调用之前和之后实施拦截并执行某段代码,也可以完全阻止某个Action对象方法的调用。

拦截器主要的功能是将Action中的一些通用的功能提取出来,定义在拦截器中。当某个Action需要该功能时,只需要给该Action指定对应的拦截器即可。从而提高了代码的可重用性,也实现类装配式和可插拔式的体系结构,使得整个系统结构更加灵活。

拦截器的特点:1、简化Action的实现,将重复的单一功能从Action中剥离出来;2、每个拦截器只实现某一种特定的功能;3、拦截器的出现实现了代码的模块化编程(可插拔式编程);4、拦截器整合了Actoin中重复的代码(功能),提高了代码的复用性。

拦截器栈Interceptor Stack,是将多个拦截器按照一定的顺序连接形成的一条链。当给某个Action对象指定了拦截器栈,则在该Action对象方法被调用时,会按照拦截器栈中的先后顺序来指定拦截的拦截方法。

struts内置的拦截器都定义在struts-core-?.jar包中的struts-default.xml文件中:

图片发自简书App

params拦截器,用于把请求参数设置到相应的Action对象的属性中,并自动进行类型转换;

modelDriven拦截器,用于将getModel()方法返回的模型对象存入到OgnlValueStack中。一般只需要让Action实现ModelDriven接口,即可使得Action配置了该拦截器;

exception拦截器,用在抛异常的时候,用于捕获异常;

validation拦截器,用于读取项目中的*-validation.xml文件,并使得这些文件中声明的校验生效;

token拦截器,用于核对当前Action请求是否有效,防止重复提交Action请求;

fileUpload拦截器,用于处理文件上传;

workflow拦截器,用于校验,调用Action的validate方法,如果有错误,则重新定位到名为“input”的结果视图;

servletConfig拦截器,用于通过感知接口,获取感应的对象,如HttpServletRequest, HttpServletResponse,HttpSession等;

14、自定义一个拦截器

开发步骤:

图片发自简书App

2)定义一个Action,如HelloAction,实现了ActionSupport接口。

在struts.xml文件中配置如下,注意由于拦截器返回了login,所以也要配置。当然我们可以直接把login这个result配置到中,但是这样的话,每个用到该拦截器的action都要配置login。显然,我们应该将login配置为一个全局的result。

3)接下来就是对拦截器的配置,拦截器的配置其实也是在struts.xml文件中配置的:

首先要通过节点来定义拦截器,即指定对应的类和名字:

在上图中,我们可以在节点中定义一个拦截器栈,在该栈中将默认的拦截器栈与我们所定义的拦截器组合成一个新的拦截器栈。注意顺序,先是defaultStack,然后才是我们定义的拦截器。如上图。

然后通过节点来指定该包下面的action都同一应用的拦截器。如:

注意:如果要对某一个action不使用我们自定义的拦截器栈,则需要在节点中声明使用默认的拦截器栈。即:

15、国际化i18n

所谓的国际化,指的是让程序能够支持在不同的语言环境下显示不同语言的内容。如在中文环境下显示中文内容,在英文语言环境下显示英文内容。

struts2支持国际化,是由于其内置有i18n的拦截器:

那么如何才能在项目中配置国际化的语言内容呢?

图片发自简书App

首先分别定义不同语言环境对应的properties文件,如文件名为constant,则中文环境下的properties文件全名应该为constant_zh_CN.properties,英文环境下的properties文件的全名为constant_en_US.properties。

然后在struts中配置国际化文件:

最后在jsp文件下要用的话,必须使用taglib命令引入struts标签库:

在需要从properties文件中拿常量的地方,使用即可拿到key对应的值。

注意:对于properties文件中key的值,如果要在action中拿到,需要action对象实现了ActionSupport接口,通过getText(key)即可拿到key在不同语言环境中的value值。

16、OGNL是什么?

Object Graphic Navigation Language对象图导航语言,是一种表达式语言,可以理解为OGNL是EL表达式语言的一种升级版,通过简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。使用相同的表达式去存取对象的属性,取得对象中的数据。

该表达式是将对象的引用值用点串联起来,从左到右,每一次表达式计算返回的结果成为当前对象,后面部分接着在当前对象上进行计算,一直到全部表达式计算完成,返回最后得到的对象。OGNL则针对这条基本原则进行不断的扩充,从而使之支持对象树、数组、容器的访问,甚至是类似SQL中的投影选择等操作。

例如,假设当前环境(对象)中的根对象是student,而student对象中有个scores数组,该数组中第一个值为总分,则可以通过ognl表达式:studet.scores[0]拿到总分的值。

OGNL支持+-*/运算符,是struts2默认的表达式语言;支持类的静态方法的调用和值的访问;支持赋值操作和表达式串联;可以操作集合对象;可以直接new一个对象;

OGNL通常要结合Struts2的一些标志一起使用,如#,$,%;

其中#主要用来访问ONGL上下文和action上下文,#代表ActionContext.getContext()。

如#parameters.id[0]相当于request.getParameterValues("id").get(0);

#request.userName相当于request.getAttribute("userName");

#session.userName相当于session.getAttribute("userName");

#application.userName相当于application.getAttribute("userName");

attr 用于按request > session > application顺序访问其属性(attribute) #attr.userName相当于按顺序在以上三个范围(scope)内读取userName属性,直到找到为止;

构造Map,如#{'foo1':'bar1', 'foo2':'bar2'};

17、ValueStack值栈

本质上是Struts2的接口com.opensymphony.xwork2.util.ValueStack,Struts容器使用com.opensymphony.xwork2.ognl.OgnlValueStack来封装每一次请求的数据,也就是说每一次请求都会创建一个新的ValueStack对象,在该对象中封装了本次请求的相关数据信息。也意味着Struts2中的ValueStack对象可以使用ognl表达式来存取其中的数据。那么ValueStack对象可以通过request.getAttribute("struts.valueStack")的方式拿到,也可以通过调用ActionContext对象的getValueStack()方法来拿到。也就是意味着在request对象中有一个名为struts.valueStack的key,其对应的值为ValueStack对象。

我们先看看源码:

图片发自简书App

其实OgnlValueStack对象的栈结构的实现,是由其里面的root变量来实现的。

图片发自简书App

我们可以在root中拿到当前Action的对象,在context中拿到作用域对象(request,session,application)的Map格式的对象及参数对象parameters等。

如果要获取Action对象中的属性的值,由于Action对象本身在root的栈顶位置,所以在jsp中可以通过来直接获取;

注意:

1) 有一种情况会导致Action不在栈顶位置,例如当前Action中new了一个对象作为其属性的值。此时,可以在context的key为params中拿到值。

2) 如果需要从root栈的栈顶拿值,则可以通过如下方式在jsp中拿到:

如果要获取的值对应的key在ValueStack的context中,则在jsp中可以通过的方式获取。

把数据放入ValueStack的root中:valueStack.getRoot().push(Object) ,valueStack.getRoot().add(0,Object),valueStack.set(key, Object),以及在Action中带有getter方法的属性。

把数据放入ValueStack的context中,valueStack.getContext().put(key, value)或者ActionContext.getContext().put(key, value)。

注意:在jsp中如果需要查看valueStack中的所有数据,则可以添加如下代码:

18、输入校验

输入校验,一般用于对jsp页面在提交(发出请求)时,在被请求的Action中执行validate()方法,开发者只需要在该方法中编写验证代码即可。如果验证不通过,只需要调用super.addFileError("字段名称","错误信息")即可在Action中业务方法被调用之前终止当前的请求。同时,需要在struts.xml文件中配置名为input的result,用来指定当输入校验失败时,需要跳转到的页面。

如果某些业务方法不需要被校验,则可以通过在该方法上添加注解@SkipValidation即可。

图片发自简书App

如果只对Action中的业务方法abc()进行调用前的校验,则validate()方法的方法名改为validateAbc()即可。

如果需要在某个方法被校验失败时跳转到的不是input视图,而是其它视图,比如abc视图。则可以在被校验的方法上通过注解来更改校验失败的视图名。如@InputConfig(resultName="abc")。

注意:

validation拦截器已经对input方法作了特殊处理,不会对其进行校验处理。

validation拦截器只是在调用input方法时执行了validate或validateXxx方法,当校验失败时,由该validate方法提供调用addFiledError()方法。在validation拦截器之后有个workflow拦截器,该拦截器会检查FiledError中是否有错误数据,如果有,就会跳转到input结果码对应的页面。

19、文件上传

使用struts2提供的文件上传组件相对来说比较容易些。

首先,在jsp页面中,引入struts2的标签库struts-tag,然后在需要上传文件的地方使用标签来让用户选择文件:

图片发自简书App

在提交到的Action中,定义三个属性,并提供对应的setter方法,然后在业务方法如execute中进行文件的copy操作即可。然后在struts.xml文件中注册此Action。

注意:文件上传要求jsp中form的请求时post,且enctype="multipart/form-data"值。struts2的form标签的method属性的默认值就是post。

我们还可以通过参数注入的方式来设置FileUploadInterceptor拦截器的允许上传文件最大值和允许上传文件类型这两个参数。

图片发自简书App

具体设置如下:

1024*1024*5

png,jpg

20、文件下载

图片发自简书App
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容