Java面试:SpringMVC工作流程

1.SpringMVC简介

SpringMVC是一种基于Spring实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,使用了MVC架构模式的思想,将web层进行职责解耦,并管理应用所需对象的生命周期,为简化日常开发,提供了很大便利。

SpringMVC提供的核心组件包括请求分发器(DispatchServlet)、请求处理映射器(HandlerMapping)、请求处理适配器(Handler Adapter)、视图解析器(View Resolver) 、动作处理器(Controller)和模型视图对象(ModelAndView),通过以上各大组件的协同工作来完成客户端的请求响应工作,使得MVC架构的设计理念得以实现。

2.SpringMVC工作流程

我们先来看一下SpringMVC的流程图
222.png

(1)客户端向web服务器(如tomcat)发送一个http请求,web服务器对http请求进行解析,解析后的url地址如果匹配到DispatchServlet的映射路径(通过web.xml中的servlet-mapping配置),web容器就会将请求交给DispatchServlet处理

(2)DispatcherServlet接收到这个请求后,再对URL进行解析,得到请求资源标识符(URI)。然后调用相应方法得到的HandlerMapping对象,再根据URI,调用这个对象的相应方法获得Handler对象以及它对应的拦截器。(在这里只是获得了Handler对象,并不会操作它,在SpringMVC中,是通过HandlerAdapter对Handler进行调用、控制的)

(3)DispatcherServlet根据得到的Handler对象,选择一个合适的HandlerAdapter,创建其实例对象,执行拦截器中的preHandler()方法。

(4)在拦截器方法中,提取请求中的数据模型,填充Handler入参,所以所有准备工作都已做好,开始执行Handler(我们写的controller代码并不是能被直接执行,需要有刚才那些操作,才能转变为Handler被执行)。

(5)Handler执行完毕后返回一个ModelAndView对象给DispatcherServlet。

(6)这个ModleAndView只是一个逻辑视图,并不是真正的视图,DispatcherServlet通过ViewResolver视图解析器将逻辑视图转化为真正的视图(通俗理解为将视图名称补全,如加上路径前缀,加上.jsp后缀,能指向实际的视图)。

(7)DispatcherServlet通过Model将ModelAndView中得到的处数据解析后用于渲染视图。将得到的最终视图通过http响应返回客户端。

3.SpringMVC组件解析

3.1 HandlerMapping

 SpringMVC体系结构中有着举足轻重的地位,充当着url和Controller之间映射关系配置的角色。主要有三部分组成:HandlerMapping映射注册、根据url获取对应的处理器、拦截器注册。
444.gif

在Spring MVC中,关于HandlerMapping的使用,主要包括两个部分:注册和查找。在HandlerMapping的实现中,持有一个handlerMap这样一个HashMap<String, Object>,其中key是http请求的path信息,value可以是一个字符串,或者是一个处理请求的HandlerExecutionChain,如果是String类型,则会将其视为Spring的bean名称。在HandlerMapping对象的创建中,IoC容器执行了一个容器回调方法setApplicationContext,在这个方法中调用initApplicationContext方法进行初始化,各个子类可以根据需求的不同覆写这个方法。关于handlerMap信息的注册就是在initApplicationContext方法中被执行的。

3.2 Handleradapter

 该组件在SpringMVC请求执行过程中包括两个部分,HandlerAdapter的注册和HandlerAdapter的执行。

(1)HandlerAdapter的注册:

DispatcherServlte会根据配置文件信息注册HandlerAdapter,如果在配置文件中没有配置,那么DispatcherServlte会获取HandlerAdapter的默认配置,如果是读取默认配置的话,DispatcherServlte会读取DispatcherServlte.properties文件,该文件中配置了三种HandlerAdapter:HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter。DispatcherServlte会将这三个HandlerAdapter对象存储到它的handlerAdapters这个集合属性中,这样就完成了HandlerAdapter的注册。

(2)HandlerAdapter的执行:

DispatcherServlte会根据handlerMapping传过来的controller与已经注册好了的HandlerAdapter一一匹配,看哪一种HandlerAdapter是支持该controller类型的,如果找到了其中一种HandlerAdapter是支持传过来的controller类型,那么该HandlerAdapter会调用自己的handle方法,handle方法运用java的反射机制执行controller的具体方法来获得ModelAndView,例如SimpleControllerHandlerAdapter是支持实现了controller接口的控制器,如果自己写的控制器实现了controller接口,那么SimpleControllerHandlerAdapter就会去执行自己写控制器中的具体方法来完成请求。

4.SpringMVC源码简单剖析

根据上边的图我们可以看到前端控制器在springMVC的系统中拥有了非常大的作用,那么我们就以此来入手。


333.png
  • 首先在它的关系图中我们可以看出,DispatcherServlet类继承FrameworkServlet类,而FrameworkServlet继承了HttpServletBean类,再往上就是HttpServlet类与Servlet类了。
  • 那么我们就可以得出DispatchServlet是一个servlet类,既然是一个Servlet那么久有一个核心方法:Service()方法,我们在DispatchServlet中找,发现没有,那我们继续去它的父类中找,于是发现了如下方法:
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (HttpMethod.PATCH != httpMethod && httpMethod != null) {
        super.service(request, response);
    } else {
        this.processRequest(request, response);
    }
}
  • 它首先判断是不是PATCH请求,如果是的话执行父类的service()方法,如果不是执行 processRequest(),那我们继续追踪:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = this.buildLocaleContext(request);
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor(null));
        this.initContextHolders(request, localeContext, requestAttributes);
        try {
            this.doService(request, response);
        } catch (IOException | ServletException var16) {
            failureCause = var16;
            throw var16;
        } catch (Throwable var17) {
            failureCause = var17;
            throw new NestedServletException("Request processing failed", var17);
        } finally {
            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            if (this.logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", (Throwable)failureCause);
                } else if (asyncManager.isConcurrentHandlingStarted()) {
                    this.logger.debug("Leaving response open for concurrent processing");
                } else {
                    this.logger.debug("Successfully completed request");
                }
            }

            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
        }
    }
  • 这里边的代码主要是执行一些控制器等等,可以不用了解太多,我们主要是了解其执行流程。在其中我们可以看到 this.doService(request, response);方法,来执行一些事务,那么我们继续追踪:
protected abstract void doService(HttpServletRequest var1, HttpServletResponse var2) throws Exception;

  • 我们发现它是调用了子类:DispatcherServlet类中的方法,继续追踪:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (this.logger.isDebugEnabled()) {
        String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
        this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
    }
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap();
        Enumeration attrNames = request.getAttributeNames();

        label112:
        while(true) {
            String attrName;
            do {
                if (!attrNames.hasMoreElements()) {
                    break label112;
                }
                attrName = (String)attrNames.nextElement();
            } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

            attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
    }
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }

        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }
    try {
        this.doDispatch(request, response);
    } finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
            this.restoreAttributesAfterInclude(request, attributesSnapshot);
        }

    }
}

  • 我们看到它首先设置了一些request属性,然后执行了一个this.doDispatch(request, response)方法,至此我们算是找到了它的核心执行代码:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;
            try {
                //这个方法下面有单独说明
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                 //这个方法下面有单独说明
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
                //这个方法下面有单独说明
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                 //这个方法下面有单独说明
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                 //这个方法下面有单独说明
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                this.applyDefaultViewName(processedRequest, mv);
                //这个方法下面有单独说明
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }
            //这个方法下面有单独说明
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}
  • 这段代码可以分成四个部分:

①查找Handler对应的HandlerMapping部分:

try {
    processedRequest = this.checkMultipart(request);
    multipartRequestParsed = processedRequest != request;
    mappedHandler = this.getHandler(processedRequest);
    if (mappedHandler == null) {
        this.noHandlerFound(processedRequest, response);
        return;
    }

它首先判断request请求是不是二进制请求,然后执行this.getHandler(processedRequest);方法。那么我们来了解一下这个方法:

②this.getHandler(processedRequest)部分

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        Iterator var2 = this.handlerMappings.iterator();
        while(var2.hasNext()) {
            HandlerMapping hm = (HandlerMapping)var2.next();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

首先:我们发现它获取了一个HandlerMapping的迭代器,然后查找每一个HandlerMapping。那么它为什么要先去查找HandlerMapping呢?那是因为我们在配置Controller的时候有两种方式:xml和注解的方式,所以它先去找到HandlerMapping,然后根据HandlerMapping去找Handler,就是hm.getHandler(request);这个方法来查找Handler。

③查找HandlerAdapter部分:

HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
    }
    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
        return;
    }
}

④在拿到HandlerMapping之后,开始查找HandlerAdapter,就是ha.getLastModified(request, mappedHandler.getHandler());来查找。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();
            while(var2.hasNext()) {
                HandlerAdapter ha = (HandlerAdapter)var2.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Testing handler adapter [" + ha + "]");
                }
                if (ha.supports(handler)) {
                    return ha;
                }
            }
        }
        throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }

它的查找方式与之前查找HandlerMapping类似。

  • 拦截器执行部分:
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = this.getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }
    }
    return true;
}

以上一小段代码就是获取拦截器然后执行。

  • 再接下来就是最重要的Hanler执行部分,它返回的对象是mv,就是ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
    return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
    dispatchException = var20;
} catch (Throwable var21) {
    dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
//这个方法下面有说明
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

它首先在 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 执行了handler并返回了一个视图;然后通过 this.applyDefaultViewName(processedRequest, mv); 在我们没有设置视图名称的时候返回默认视图名;然后通过 this.applyDefaultViewName(processedRequest, mv); 获取到项目的拦截器,然后判断拦截器是否存在,存在则对该请求进行测试(postHandler),如果有一个拦截器不成功,则请求失败。最后来到视图解析和渲染的执行方法: this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            this.logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException)exception).getModelAndView();
        } else {
            Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
            mv = this.processHandlerException(request, response, handler, exception);
            errorView = mv != null;
        }
    }
    if (mv != null && !mv.wasCleared()) {
        //这个方法下面有说明
        this.render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else if (this.logger.isDebugEnabled()) {
        this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
    }
    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }

    }
}

它首先判断一下是否异常,如果出现异常,判断异常是否是ModelAndViewDefiningException的实例,如果是就直接调用getModelAndView方法返回一个mv,否则调用processHanlderException方法初始化mv对象;如果没有异常出现,那么判断mv是否为空,为空抛出异常,不为空则判断mv,wasCleared()方法返回的值是否为真;判断完成以后,执行了 this.render(mv, request, response);方法。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
    response.setLocale(locale);
    String viewName = mv.getViewName();
    View view;
    if (viewName != null) {
        view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
        }
    } else {
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
        }
    }
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
    }

    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        view.render(mv.getModelInternal(), request, response);
    } catch (Exception var8) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var8);
        }

        throw var8;
    }
}

它首先设置了视图的编码格式,然后通过 view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);进行了视图解析:

public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + " and static attributes " + this.staticAttributes);
    }
    Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);
    this.prepareResponse(request, response);
    this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
}

它首先通过 Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);获取了传入的model的map集合。然后通过 this.prepareResponse(request, response);设置响应头的两个参数,最后通过 this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);将mergeModel合到请求里.

protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    this.exposeModelAsRequestAttributes(model, request);
    this.exposeHelpers(request);
    String dispatcherPath = this.prepareForRendering(request, response);
    RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl() + "]: Check that the corresponding file exists within your web application archive!");
    } else {
        if (this.useInclude(request, response)) {
            response.setContentType(this.getContentType());
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Including resource [" + this.getUrl() + "] in InternalResourceView '" + this.getBeanName() + "'");
            }

            rd.include(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Forwarding to resource [" + this.getUrl() + "] in InternalResourceView '" + this.getBeanName() + "'");
            }

            rd.forward(request, response);
        }

    }
}

它首先进行了 this.exposeModelAsRequestAttributes(model, request);操作:

protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
    model.forEach((modelName, modelValue) -> {
        if (modelValue != null) {
            request.setAttribute(modelName, modelValue);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() + "] to request in view with name '" + this.getBeanName() + "'");
            }
        } else {
            request.removeAttribute(modelName);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Removed model object '" + modelName + "' from request in view with name '" + this.getBeanName() + "'");
            }
        }

    });
}

由此可见,model中的数据,最终是放在request域中的。

然后他们进行了RequestDispatcher 对象配置,就是选择它要进行的操作了,是使用include还是forward进行跳转。至此,视图的解析与渲染工作都完成了,并且也发送到了前端页面,完成了一个请求的整个流程。

5.总结

SpringMVC的总体执行流程的源码解析就为以上描述,我们发现SpringMVC最主要的控制器类DispatcherServlet,它在项目的web.xml中配置拦截地址。当我们发送请求时就可以进入到以上代码中,从而完成整个请求处理。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容