springmvc源碼分析-HandlerAdapter原理

springmvc中HandlerAdapter用于執(zhí)行具體的Handler萧恕,也就是controller,是springmvc中一個(gè)特別重要的組件重慢,先來看下接口定義的方法

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

方法的整體邏輯就是解析request的參數(shù),傳遞給handler逊躁,執(zhí)行后拿到結(jié)果后似踱,寫入到response

springmvc內(nèi)置的HandlerAdapter有好幾種,本文來看下最常用的RequestMappingHandlerAdapter,這個(gè)對(duì)象也是在spring容器初始化后便完成了核芽,定義在WebMVCConfigurationSupport中

public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
        @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcValidator") Validator validator) {

    RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
    adapter.setContentNegotiationManager(contentNegotiationManager);
    adapter.setMessageConverters(getMessageConverters());
    adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
    adapter.setCustomArgumentResolvers(getArgumentResolvers());
    adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

    if (jackson2Present) {
        adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
        adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
    }

    AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
    configureAsyncSupport(configurer);
    if (configurer.getTaskExecutor() != null) {
        adapter.setTaskExecutor(configurer.getTaskExecutor());
    }
    if (configurer.getTimeout() != null) {
        adapter.setAsyncRequestTimeout(configurer.getTimeout());
    }
    adapter.setCallableInterceptors(configurer.getCallableInterceptors());
    adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

    return adapter;
}

從這可以看出HandlerAdapter主要是維護(hù)一些對(duì)請(qǐng)求參數(shù)和響應(yīng)對(duì)象的數(shù)據(jù)處理囚戚,消息轉(zhuǎn)化器messageConverter,參數(shù)解析器argumentResolver轧简,返回值處理器returnValueHandler驰坊,這三者的關(guān)系等下分析

protected final List<HttpMessageConverter<?>> getMessageConverters() {
    if (this.messageConverters == null) {
        this.messageConverters = new ArrayList<>();
        configureMessageConverters(this.messageConverters);
        if (this.messageConverters.isEmpty()) {
            addDefaultHttpMessageConverters(this.messageConverters);
        }
        extendMessageConverters(this.messageConverters);
    }
    return this.messageConverters;
}

這里有個(gè)比較重要的方法configureMessageConverters,用于子類重寫哮独,用于擴(kuò)展messageConverters拳芙,參數(shù)解析器和返回值處理器也是差不多的邏輯,都會(huì)有一個(gè)擴(kuò)展方法皮璧,給應(yīng)用程序添加

    protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {
        if (this.argumentResolvers == null) {
            this.argumentResolvers = new ArrayList<>();
            addArgumentResolvers(this.argumentResolvers);
        }
        return this.argumentResolvers;
    }

    protected final List<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
        if (this.returnValueHandlers == null) {
            this.returnValueHandlers = new ArrayList<>();
            addReturnValueHandlers(this.returnValueHandlers);
        }
        return this.returnValueHandlers;
    }

如果當(dāng)前項(xiàng)目存在Jackson舟扎,那么還會(huì)添加兩個(gè)BodyAdvice,分別為JsonViewRequestBodyAdvice和JsonViewResponseBodyAdvice恶导,這個(gè)在后面的工作流程再介紹這兩者的作用

當(dāng)生成bean對(duì)象后浆竭,由于RequestMappingHandlerAdaptor也實(shí)現(xiàn)了InitializingBean接口,所以還會(huì)執(zhí)行初始化方法afterPropertiesSet

public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();

    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

其中惨寿,initControllerAdviceCache方法就是查詢?nèi)萜髦凶⒔饬薂ControllerAdvice的類邦泄,然后根據(jù)三種情況進(jìn)行處理,如下

private void initControllerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }
//查找注解了@ControllerAdvice的bean列表
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

    List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
//查詢bean中是否有注解@ModelAttribute的方法
        Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
        if (!attrMethods.isEmpty()) {
            this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
        }
//查詢bean中是否有注解@InitBinder的方法
        Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
        if (!binderMethods.isEmpty()) {
            this.initBinderAdviceCache.put(adviceBean, binderMethods);
        }
//如果該類是RequestBodyAdvice或者ResponseBodyAdvice的裂垦,會(huì)維護(hù)到HandlerAdapter的成員屬性中
        if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
    }

    if (!requestResponseBodyAdviceBeans.isEmpty()) {
        this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    }
}

到這邊顺囊,RequestMappingHandlerAdapter實(shí)例化和初始化完成了,然后添加到spring容器中蕉拢,需要特別注意的是argumentResolvers 特碳、returnValueHandlers 、messageConverters 晕换、@ControllerAdvice午乓,這幾個(gè)在執(zhí)行handler過程中發(fā)揮了核心作用。

下面以一個(gè)實(shí)際的例子來跟蹤下RequestMappingHandlerAdapter的工作流程

@ResponseBody
    @RequestMapping(method = RequestMethod.POST, value = "/getStudent")
    public GetStudentResp getStudent(@RequestBody GetStudentParam param) {
        Student student = new Student();
        student.setName("hello" + param.getUserId());
        GetStudentResp resp = new GetStudentResp();
        resp.setStudent(student);
        return resp;
    }

當(dāng)有請(qǐng)求到來時(shí)闸准,會(huì)先經(jīng)過DispatcherServlet的doDispatch方法益愈,然后HandMapping會(huì)拿到了HandlerExecutionChain對(duì)象,接著springmvc會(huì)拿到一個(gè)HandlerAdapter來執(zhí)行HandlerExecutionChain的HandlerMethod

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            for (HandlerAdapter adapter : this.handlerAdapters) {
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
    public final boolean supports(Object handler) {
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }

當(dāng)拿到RequestMappingHandlerAdaptor對(duì)象后夷家,便根據(jù)request執(zhí)行具體的HandlerMethod了

ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

一直跟進(jìn)handle方法

@Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
//將HandlerMethod包裝成ServletInvocableHandlerMethod 蒸其,并添加argumentResolvers 和returnValueHandlers 
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
        
//結(jié)果容器
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        //根據(jù)webRequest 執(zhí)行invocableMethod,然后將結(jié)果存儲(chǔ)在mavContainer 中
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

這個(gè)方法省略了很多源碼库快,暫時(shí)先不考慮InitBinder和ModelAttribute摸袁,只關(guān)注最核心部分

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        setResponseStatus(webRequest);

        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
                disableContentCachingIfNecessary(webRequest);
                mavContainer.setRequestHandled(true);
                return;
            }
        }
        else if (StringUtils.hasText(getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }

        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");
        try {
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(formatErrorForReturnValue(returnValue), ex);
            }
            throw ex;
        }
    }

invokeForRequest會(huì)根據(jù)argumentResolvers解析http的請(qǐng)求輸入流,轉(zhuǎn)成Controller方法需要的參數(shù)列表义屏,然后根據(jù)拿到的參數(shù)執(zhí)行方法靠汁,返回結(jié)果

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
//獲取參數(shù)列表
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    return doInvoke(args);
}
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
//根據(jù)HandlerMethod拿到對(duì)應(yīng)的參數(shù)列表蜂大,若為空,則返回
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }
    Object[] args = new Object[parameters.length];
//循環(huán)parameters蝶怔,獲取每個(gè)參數(shù)值
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
//若argumentResolvers沒有一個(gè)能支持解析當(dāng)前parameter县爬,便會(huì)拋異常
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
//若支持了,再根據(jù)解析器進(jìn)行參數(shù)值的解析并獲取
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
        
        }
    }
    return args;
}

先來看下判斷解析器是否能解析當(dāng)前參數(shù)的邏輯

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

會(huì)先從緩存獲取添谊,緩存不為null,直接返回察迟,為空斩狱,再循環(huán)每個(gè)resolver,拿到支持解析的resolver后加入緩存便返回
上面這個(gè)例子中扎瓶,參數(shù)是用@RequestBody注解的所踊,因此最終會(huì)有這個(gè)解析器RequestResponseBodyMethodProcessor,判斷的邏輯也很簡單概荷,就是看參數(shù)是否有@RequestBody注解,因此接下來秕岛,看下這個(gè)解析器對(duì)參數(shù)的處理過程

    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        parameter = parameter.nestedIfOptional();
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        return adaptArgumentIfNecessary(arg, parameter);
    }

很清晰的看出,resolver對(duì)參數(shù)的解析實(shí)際上是通messageConverter來完成的误证,也就是在HandlerAdapter初始化過程中添加的converter

    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
            Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

        MediaType contentType;
        boolean noContentType = false;
        try {
            contentType = inputMessage.getHeaders().getContentType();
        }
        catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotSupportedException(ex.getMessage());
        }
        Class<?> contextClass = parameter.getContainingClass();
        Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
        HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
        Object body = NO_VALUE;

        EmptyBodyCheckingHttpInputMessage message;
        try {
            message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

            for (HttpMessageConverter<?> converter : this.messageConverters) {
                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
                GenericHttpMessageConverter<?> genericConverter =
                        (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
                if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
                        (targetClass != null && converter.canRead(targetClass, contentType))) {
                    if (message.hasBody()) {
                        HttpInputMessage msgToUse =
                                getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                        body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                                ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
                        body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                    }
                    else {
                        body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
                    }
                    break;
                }
            }
        }
        catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
        }

        if (body == NO_VALUE) {
            if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
                    (noContentType && !message.hasBody())) {
                return null;
            }
            throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
        }
        return body;
    }

這個(gè)方法的執(zhí)行步驟

  1. 獲取messageConverter继薛,執(zhí)行canRead方法,判斷是否可以從http請(qǐng)求讀取數(shù)據(jù)
  2. 執(zhí)行注解了@ControllerAdvice的RequestBodyAdvice類的beforeBodyRead方法
  3. 執(zhí)行messageConverter的read方法愈捅,獲取請(qǐng)求參數(shù)對(duì)象
  4. 執(zhí)行注解了@ControllerAdvice的RequestBodyAdvice類的afterBodyRead方法

看到這遏考,便可知道,若程序要在請(qǐng)求數(shù)據(jù)達(dá)到controller前做一些處理蓝谨,可以通過三個(gè)地方來實(shí)現(xiàn)灌具,也就是上述的2、3譬巫、4個(gè)步驟咖楣。這個(gè)在一些特殊場(chǎng)景是很有用的,比如前端數(shù)據(jù)要做一些加密芦昔,然后再后端controller接收到時(shí)希望是明文的诱贿,便可這樣處理。

當(dāng)參數(shù)解析器根據(jù)http請(qǐng)求解析出參數(shù)對(duì)象后烟零,執(zhí)行controller的方法瘪松,獲取到結(jié)果,會(huì)執(zhí)行returnValueHandler的handleReturnValue锨阿,需要將結(jié)果寫入到http的輸出流當(dāng)中

this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

    private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
        boolean isAsyncValue = isAsyncReturnValue(value, returnType);
        for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
            if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
                continue;
            }
            if (handler.supportsReturnType(returnType)) {
                return handler;
            }
        }
        return null;
    }

最終會(huì)找到這個(gè)RequestResponseBodyMethodProcessor宵睦,內(nèi)部也是通過messageConverter將數(shù)據(jù)寫入到http輸出流的,根據(jù)方法墅诡,會(huì)看到這個(gè)邏輯

if (selectedMediaType != null) {
    selectedMediaType = selectedMediaType.removeQualityValue();
    for (HttpMessageConverter<?> converter : this.messageConverters) {
        GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                (GenericHttpMessageConverter<?>) converter : null);
        if (genericConverter != null ?
                ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                converter.canWrite(valueType, selectedMediaType)) {
            body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                    (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                    inputMessage, outputMessage);
            if (body != null) {
                Object theBody = body;
                addContentDispositionHeader(inputMessage, outputMessage);
                if (genericConverter != null) {
                    genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                }
                else {
                    ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                }
            }
            else {
            }
            return;
        }
    }
}

這里重點(diǎn)關(guān)注下這幾個(gè)步驟

  1. 根據(jù)messageConverter壳嚎,判斷canWrite方法桐智,若返回true
  2. 執(zhí)行注解了@ControllerAdvice的ResponseBodyAdvice的beforeBodyWrite方法
  3. 執(zhí)行messageConverter的write方法

這里也需要注意的一個(gè)是,程序可以通過在beforeBodyWrite方法內(nèi)對(duì)返回對(duì)象做一些處理烟馅,修改數(shù)據(jù)说庭,然后再寫入到http輸出流當(dāng)中。

總結(jié)郑趁,HandlerAdapter最主要的三個(gè)屬性值刊驴,argumentResolver,messageConverter寡润,returnValueHandler捆憎,argumentResolver內(nèi)部使用messageConverter將http輸入流數(shù)據(jù)轉(zhuǎn)成controller對(duì)應(yīng)的參數(shù)對(duì)象,然后執(zhí)行controller的方法梭纹,拿到controller方法返回的結(jié)果后躲惰,returnValueHandler內(nèi)部使用messageConverter將controller返回的結(jié)果寫入到http輸出流中

還有個(gè)要注意的地方便是可以通過RequestBodyAdvice和ResponseBodyAdvice對(duì)請(qǐng)求和響應(yīng)做一些特殊處理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末变抽,一起剝皮案震驚了整個(gè)濱河市础拨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绍载,老刑警劉巖诡宗,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異击儡,居然都是意外死亡僚焦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門曙痘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芳悲,“玉大人,你說我怎么就攤上這事边坤∶福” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵茧痒,是天一觀的道長肮韧。 經(jīng)常有香客問我,道長旺订,這世上最難降的妖魔是什么弄企? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮区拳,結(jié)果婚禮上拘领,老公的妹妹穿的比我還像新娘。我一直安慰自己樱调,他們只是感情好约素,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布届良。 她就那樣靜靜地躺著,像睡著了一般圣猎。 火紅的嫁衣襯著肌膚如雪士葫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天送悔,我揣著相機(jī)與錄音慢显,去河邊找鬼。 笑死欠啤,一個(gè)胖子當(dāng)著我的面吹牛鳍怨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跪妥,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼声滥!你這毒婦竟也來了眉撵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤落塑,失蹤者是張志新(化名)和其女友劉穎纽疟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憾赁,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡污朽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了龙考。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟆肆。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晦款,靈堂內(nèi)的尸體忽然破棺而出炎功,到底是詐尸還是另有隱情,我是刑警寧澤缓溅,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布蛇损,位于F島的核電站,受9級(jí)特大地震影響坛怪,放射性物質(zhì)發(fā)生泄漏淤齐。R本人自食惡果不足惜填具,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一碘裕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镀钓,春花似錦居灯、人聲如沸锈死。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽待牵。三九已至其屏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缨该,已是汗流浹背偎行。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贰拿,地道東北人蛤袒。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像膨更,于是被迫代替她去往敵國和親妙真。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容