Spring MVC請求處理(三) - HandlerMapping(二)

本文分析RequestMappingInfoHandlerMapping類和RequestMappingHandlerMapping類骤铃。

RequestMappingInfoHandlerMapping類

RequestMappingInfoHandlerMapping類繼承了AbstractHandlerMethodMapping類,利用RequestMappingInfo類充當了映射的角色。

public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {

    protected RequestMappingInfoHandlerMapping() {
        setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
    }

    @Override
    protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
        return info.getPatternsCondition().getPatterns();
    }

    @Override
    protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
        return info.getMatchingCondition(request);
    }

    @Override
    protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
        return new Comparator<RequestMappingInfo>() {
            @Override
            public int compare(RequestMappingInfo info1, RequestMappingInfo info2) {
                return info1.compareTo(info2, request);
            }
        };
    }

    // 省略一些代碼
}

有匹配

上一篇文章提到AbstractHandlerMethodMapping類的handleMatch相當于回調函數(shù),表示有匹配的處理器時的執(zhí)行動作,RequestMappingInfoHandlerMapping類重寫了handleMatch方法。

@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
    super.handleMatch(info, lookupPath, request);

    String bestPattern;
    Map<String, String> uriVariables;
    Map<String, String> decodedUriVariables;

    Set<String> patterns = info.getPatternsCondition().getPatterns();
    if (patterns.isEmpty()) {
        bestPattern = lookupPath;
        uriVariables = Collections.emptyMap();
        decodedUriVariables = Collections.emptyMap();
    }
    else {
        bestPattern = patterns.iterator().next();
        uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
        decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
    }

    request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
    request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);

    if (isMatrixVariableContentAvailable()) {
        Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
        request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
    }

    if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
        Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
        request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
    }
}
  • 調用父類AbstractHandlerMethodMapping類的handleMatch方法;
  • 獲得匹配映射的URL模式磨总,根據請求路徑提取并解碼URI模板參數(shù)(即路徑模式中{}表示的參數(shù)),將解碼后的模板參數(shù)填充到請求中傳遞下去笼沥;
  • 提取并解碼URL矩陣參數(shù)蚪燕,將解碼后的矩陣參數(shù)填充到請求中傳遞下去娶牌。

無匹配

上一篇文章提到AbstractHandlerMethodMapping類的handleNoMatch相當于回調函數(shù),表示無匹配的處理器時的執(zhí)行動作馆纳,RequestMappingInfoHandlerMapping類重寫了handleNoMatch方法诗良,針對不匹配的原因分別拋出不同的異常。

@Override
protected HandlerMethod handleNoMatch(
        Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {

    PartialMatchHelper helper = new PartialMatchHelper(infos, request);
    if (helper.isEmpty()) {
        return null;
    }

    if (helper.hasMethodsMismatch()) {
        Set<String> methods = helper.getAllowedMethods();
        if (HttpMethod.OPTIONS.matches(request.getMethod())) {
            HttpOptionsHandler handler = new HttpOptionsHandler(methods);
            return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
        }
        throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
    }

    if (helper.hasConsumesMismatch()) {
        Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
        MediaType contentType = null;
        if (StringUtils.hasLength(request.getContentType())) {
            try {
                contentType = MediaType.parseMediaType(request.getContentType());
            }
            catch (InvalidMediaTypeException ex) {
                throw new HttpMediaTypeNotSupportedException(ex.getMessage());
            }
        }
        throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(mediaTypes));
    }

    if (helper.hasProducesMismatch()) {
        Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
        throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(mediaTypes));
    }

    if (helper.hasParamsMismatch()) {
        List<String[]> conditions = helper.getParamConditions();
        throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
    }

    return null;
}

RequestMappingHandlerMapping類

經過層層分析終于來到了RequestMappingHandlerMapping類鲁驶,它繼承了RequestMappingInfoHandlerMapping抽象類鉴裹,重寫了一些與映射有關的方法。

成員變量

RequestMappingHandlerMapping類的成員變量都與自己有關钥弯,其代碼如下所示:

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {

    private boolean useSuffixPatternMatch = true;

    private boolean useRegisteredSuffixPatternMatch = false;

    private boolean useTrailingSlashMatch = true;

    private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

    private StringValueResolver embeddedValueResolver;

    private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();


    public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
        this.useSuffixPatternMatch = useSuffixPatternMatch;
    }

    public void setUseRegisteredSuffixPatternMatch(boolean useRegisteredSuffixPatternMatch) {
        this.useRegisteredSuffixPatternMatch = useRegisteredSuffixPatternMatch;
        this.useSuffixPatternMatch = (useRegisteredSuffixPatternMatch || this.useSuffixPatternMatch);
    }

    public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
        this.useTrailingSlashMatch = useTrailingSlashMatch;
    }

    public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
        Assert.notNull(contentNegotiationManager, "ContentNegotiationManager must not be null");
        this.contentNegotiationManager = contentNegotiationManager;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.embeddedValueResolver = resolver;
    }

    // 省略一些代碼
}
  • useSuffixPatternMatch表示是否啟用后綴模式匹配径荔,默認啟用,若啟用那么映射到/users的方法也匹配/users.*(這里的點號就是實際的點號脆霎,不是正則表達式的元字符总处,星號才表示任意匹配,Ant-Style模式)睛蛛;
  • useRegisteredSuffixPatternMatch表示是否啟用注冊后綴模式匹配辨泳,默認禁用,若啟用那么后綴模式匹配只針對顯式注冊到內容協(xié)商管理器的路徑擴展名玖院。啟用useRegisteredSuffixPatternMatch會啟用useSuffixPatternMatch;
  • useTrailingSlashMatch表示是否啟用末尾斜線匹配第岖,默認啟用难菌,若啟用那么映射到/users的方法也匹配/users/;
  • 上述三個屬性可以繼承WebMvcConfigurerAdapter類并重寫configurePathMatch方法進行顯式配置蔑滓,如:
    @Configuration
    public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
    
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer.setUseSuffixPatternMatch(true);
            configurer.setUseRegisteredSuffixPatternMatch(true);
            configurer.setUseTrailingSlashMatch(true);
        }
    }
    

初始化

RequestMappingHandlerMapping類重寫了AbstractHandlerMethodMapping類的afterPropertiesSet方法郊酒,先將各個屬性保存到配置,然后調用AbstractHandlerMethodMapping類的afterPropertiesSet發(fā)現(xiàn)被@RequestMapping注解修飾的方法键袱。

@Override
public void afterPropertiesSet() {
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());

    super.afterPropertiesSet();
}

AbstractHandlerMethodMapping類的isHandler抽象方法用于在發(fā)現(xiàn)HandlerMethod的過程中判斷bean是否需要被掃描以發(fā)現(xiàn)其中的HandlerMethod燎窘,RequestMappingHandlerMapping類重寫的isHandler方法如下。從代碼可以看到對RequestMappingHandlerMapping類來說蹄咖,只有bean被@Controller注解或者@RequestMapping注解修飾時才會被掃描褐健。

@Override
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

上一篇文章提到若Java方法是所需的處理器那么AbstractHandlerMethodMapping類的getMappingForMethod抽象方法為其生成映射并返回,否則返回null澜汤。那么為什么只有被@RequestMapping注解修飾的方法才是處理器呢蚜迅?答案在RequestMappingHandlerMapping類重寫的getMappingForMethod方法中:

@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            info = typeInfo.combine(info);
        }
    }
    return info;
}

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    RequestCondition<?> condition = (element instanceof Class ?
            getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
    return null;
}

protected RequestCondition<?> getCustomMethodCondition(Method method) {
    return null;
}

protected RequestMappingInfo createRequestMappingInfo(
        RequestMapping requestMapping, RequestCondition<?> customCondition) {

    return RequestMappingInfo
            .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
            .methods(requestMapping.method())
            .params(requestMapping.params())
            .headers(requestMapping.headers())
            .consumes(requestMapping.consumes())
            .produces(requestMapping.produces())
            .mappingName(requestMapping.name())
            .customCondition(customCondition)
            .options(this.config)
            .build();
}
  1. createRequestMappingInfo方法的作用:

    • 調用AnnotatedElementUtils.findMergedAnnotation將Java元素(@RequestMapping可以用于類或方法)上的注解合并到@RequestMapping注解上,之所以這么做是因為Spring4.3增加了@GetMapping衣式、@PostMapping等簡化@RequestMapping的注解门怪;
    • 可以通過getCustomTypeCondition和getCustomMethodCondition兩個方法分別自定義被注解類和被注解方法的請求匹配條件略水;
    • 若@RequestMapping注解不存在,返回null表明該元素不是處理器刹帕,否則返回映射吵血;
  2. getMappingForMethod首先為方法創(chuàng)建映射,若該方法是處理器則為該方法所在類創(chuàng)建映射并合并到方法的映射上偷溺。

查找匹配的HandlerMethod

RequestMappingHandlerMapping是如何具體查找匹配處理器方法的呢蹋辅?上文末尾提到AbstractHandlerMethodMapping類提到getMatchingMapping抽象方法用來檢查處理器方法映射是否與請求相匹配,若匹配則返回一個與當前請求有關的映射亡蓉,否則返回null晕翠。RequestMappingHandlerMapping類的getMatchingMapping定義在其父類RequestMappingInfoHandlerMapping類中,代碼如下所示:

@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
    return info.getMatchingCondition(request);
}

可以看到該方法將判斷過程委托給了RequestMappingInfo的getMatchingCondition方法砍濒,下面重點看一下RequestMappingInfo類淋肾。

RequestMappingInfo類

RequestMappingInfo類表示@RequestMapping注解有關的信息。

成員變量

RequestMappingInfo類的成員變量和構造函數(shù)如下所示爸邢,可以看到RequestMappingInfo類的成員變量幾乎都是RequestCondition樊卓,與@RequestMapping注解的各個屬性相對應,從變量名即可分辨出用途杠河。

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {

    private final String name;

    private final PatternsRequestCondition patternsCondition;

    private final RequestMethodsRequestCondition methodsCondition;

    private final ParamsRequestCondition paramsCondition;

    private final HeadersRequestCondition headersCondition;

    private final ConsumesRequestCondition consumesCondition;

    private final ProducesRequestCondition producesCondition;

    private final RequestConditionHolder customConditionHolder;


    public RequestMappingInfo(String name, PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
            ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
            ProducesRequestCondition produces, RequestCondition<?> custom) {

        this.name = (StringUtils.hasText(name) ? name : null);
        this.patternsCondition = (patterns != null ? patterns : new PatternsRequestCondition());
        this.methodsCondition = (methods != null ? methods : new RequestMethodsRequestCondition());
        this.paramsCondition = (params != null ? params : new ParamsRequestCondition());
        this.headersCondition = (headers != null ? headers : new HeadersRequestCondition());
        this.consumesCondition = (consumes != null ? consumes : new ConsumesRequestCondition());
        this.producesCondition = (produces != null ? produces : new ProducesRequestCondition());
        this.customConditionHolder = new RequestConditionHolder(custom);
    }

    public RequestMappingInfo(PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
            ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
            ProducesRequestCondition produces, RequestCondition<?> custom) {

        this(null, patterns, methods, params, headers, consumes, produces, custom);
    }

    public RequestMappingInfo(RequestMappingInfo info, RequestCondition<?> customRequestCondition) {
        this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
                info.consumesCondition, info.producesCondition, customRequestCondition);
    }

    public String getName() {
        return this.name;
    }

    public PatternsRequestCondition getPatternsCondition() {
        return this.patternsCondition;
    }

    public RequestMethodsRequestCondition getMethodsCondition() {
        return this.methodsCondition;
    }

    public ParamsRequestCondition getParamsCondition() {
        return this.paramsCondition;
    }

    public HeadersRequestCondition getHeadersCondition() {
        return this.headersCondition;
    }

    public ConsumesRequestCondition getConsumesCondition() {
        return this.consumesCondition;
    }

    public ProducesRequestCondition getProducesCondition() {
        return this.producesCondition;
    }

    public RequestCondition<?> getCustomCondition() {
        return this.customConditionHolder.getCondition();
    }

    // 省略一些代碼
}

合并方法

combine合并方法用來將一個RequestMappingInfo合并到另一個RequestMappingInfo中碌尔,如將Controller類的@RequestMapping注解信息合并到該類各方法的@RequestMapping注解上,在RequestMappingHandlerMapping類的getMappingForMethod方法中便做了此操作(見上文)券敌。

/**
 * Combine "this" request mapping info (i.e. the current instance) with another request mapping info instance.
 * <p>Example: combine type- and method-level request mappings.
 * @return a new request mapping info instance; never {@code null}
 */
@Override
public RequestMappingInfo combine(RequestMappingInfo other) {
    String name = combineNames(other);
    PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
    RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
    ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
    HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
    ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
    ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
    RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);

    return new RequestMappingInfo(name, patterns,
            methods, params, headers, consumes, produces, custom.getCondition());
}

private String combineNames(RequestMappingInfo other) {
    if (this.name != null && other.name != null) {
        String separator = RequestMappingInfoHandlerMethodMappingNamingStrategy.SEPARATOR;
        return this.name + separator + other.name;
    }
    else if (this.name != null) {
        return this.name;
    }
    else {
        return other.name;
    }
}

匹配請求

上文提到RequestMappingHandlerMapping類在查找匹配的HandlerMethod時將判斷過程委托給了RequestMappingInfo的getMatchingCondition方法唾戚,該方法代碼如下:

/**
 * Checks if all conditions in this request mapping info match the provided request and returns
 * a potentially new request mapping info with conditions tailored to the current request.
 * <p>For example the returned instance may contain the subset of URL patterns that match to
 * the current request, sorted with best matching patterns on top.
 * @return a new instance in case all conditions match; or {@code null} otherwise
 */
@Override
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
    RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
    ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
    HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
    ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
    ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);

    if (methods == null || params == null || headers == null || consumes == null || produces == null) {
        return null;
    }

    PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
    if (patterns == null) {
        return null;
    }

    RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
    if (custom == null) {
        return null;
    }

    return new RequestMappingInfo(this.name, patterns,
            methods, params, headers, consumes, produces, custom.getCondition());
}

從代碼可以看到各個判斷條件依次調用了getMatchingCondition接口方法,若匹配則返回條件否則返回null待诅,因此只要這些條件有一個不匹配那么該RequestMappingInfo就不與請求匹配叹坦。
以PatternsRequestCondition為例,其getMatchingCondition代碼如下:

@Override
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
    if (this.patterns.isEmpty()) {
        return this;
    }

    String lookupPath = this.pathHelper.getLookupPathForRequest(request);
    List<String> matches = getMatchingPatterns(lookupPath);

    return matches.isEmpty() ? null :
        new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
                this.useTrailingSlashMatch, this.fileExtensions);
}

public List<String> getMatchingPatterns(String lookupPath) {
    List<String> matches = new ArrayList<String>();
    for (String pattern : this.patterns) {
        String match = getMatchingPattern(pattern, lookupPath);
        if (match != null) {
            matches.add(match);
        }
    }
    Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
    return matches;
}

private String getMatchingPattern(String pattern, String lookupPath) {
    if (pattern.equals(lookupPath)) {
        return pattern;
    }
    if (this.useSuffixPatternMatch) {
        if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
            for (String extension : this.fileExtensions) {
                if (this.pathMatcher.match(pattern + extension, lookupPath)) {
                    return pattern + extension;
                }
            }
        }
        else {
            boolean hasSuffix = pattern.indexOf('.') != -1;
            if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
                return pattern + ".*";
            }
        }
    }
    if (this.pathMatcher.match(pattern, lookupPath)) {
        return pattern;
    }
    if (this.useTrailingSlashMatch) {
        if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
            return pattern +"/";
        }
    }
    return null;
}
  • getMatchingCondition方法中先得到該請求的查找路徑卑雁;
  • 然后利用getMatchingPatterns方法得到與查找路徑匹配的所有形式募书,可以看到getMatchingPattern中使用了useSuffixPatternMatch、fileExtensions和useTrailingSlashMatch屬性测蹲,這些分別與RequestMappingHandlerMapping類的useSuffixPatternMatch莹捡、useRegisteredSuffixPatternMatch和useTrailingSlashMatch屬性相對應。
  • 這些屬性被賦值是因為在RequestMappingHandlerMapping為Java方法創(chuàng)建RequestMappingInfo對象時由createRequestMappingInfo方法調用了RequestMappingInfo.Builder扣甲。

總結

通過兩篇文章的深入分析篮赢,Spring MVC中@RequestMapping注解背后的原理逐漸明晰,其他HandlerMapping實現(xiàn)類大同小異琉挖,在此不再贅述荷逞。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市粹排,隨后出現(xiàn)的幾起案子种远,更是在濱河造成了極大的恐慌,老刑警劉巖顽耳,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坠敷,死亡現(xiàn)場離奇詭異妙同,居然都是意外死亡,警方通過查閱死者的電腦和手機膝迎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門粥帚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人限次,你說我怎么就攤上這事芒涡。” “怎么了卖漫?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵费尽,是天一觀的道長。 經常有香客問我羊始,道長旱幼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任突委,我火速辦了婚禮柏卤,結果婚禮上,老公的妹妹穿的比我還像新娘匀油。我一直安慰自己缘缚,他們只是感情好,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布敌蚜。 她就那樣靜靜地躺著忙灼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钝侠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天酸舍,我揣著相機與錄音帅韧,去河邊找鬼。 笑死啃勉,一個胖子當著我的面吹牛忽舟,可吹牛的內容都是我干的。 我是一名探鬼主播淮阐,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼叮阅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泣特?” 一聲冷哼從身側響起浩姥,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎状您,沒想到半個月后勒叠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兜挨,經...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年眯分,在試婚紗的時候發(fā)現(xiàn)自己被綠了拌汇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡弊决,死狀恐怖噪舀,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情飘诗,我是刑警寧澤与倡,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站疚察,受9級特大地震影響蒸走,放射性物質發(fā)生泄漏。R本人自食惡果不足惜貌嫡,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一比驻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧岛抄,春花似錦别惦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蹭秋,卻和暖如春扰付,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仁讨。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工羽莺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人洞豁。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓盐固,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丈挟。 傳聞我的和親對象是個殘疾皇子刁卜,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內容