1. spring mvc 剖析 -HandlerMapping

前言

使用spring mvc的時(shí)候,我們可以通過繼承Controller或者注解的方式進(jìn)行配置俏竞,那如何正確的找到真正處理的對象呢,這也就是本章我們要了解的內(nèi)容 - HandlerMapping.

spring mvc流程圖

此圖來源: https://www.cnblogs.com/fangjian0423/p/springMVC-directory-summary.html

源碼分析

HandlerMapping在spring mvc中所處的位置

正如我們所知道的,DispatcherServlet是spring mvc的入口方法,我們看一下源碼:

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
      //其他代碼忽略
}  

通過以上代碼刀诬,可以看出來,當(dāng)項(xiàng)目啟動(dòng)時(shí)邪财,會(huì)對初始化很多策略陕壹,其中initHandlerMappings(context)是對于HandlerMapping的初始化。

在看HandlerMapping在DispatcherServlet的使用位置:

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //其他代碼忽略
        try {
            doDispatch(request, response);
        }
        finally {
            //其他代碼忽略
        }
    }
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

//去掉了try catch以及一些驗(yàn)證的代碼

                //其他代碼忽略
                mappedHandler = getHandler(processedRequest);
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                applyDefaultViewName(processedRequest, mv);
    }
}

上述代碼中树埠,mappedHandler = getHandler(processedRequest);是我們關(guān)注的糠馆,這個(gè)代碼通過請求(HttpServeltRequest)找到對應(yīng)的HandlerMapping,從而通過HandlerMapping拿到處理器Handler。

后邊的邏輯為弥奸,根據(jù)Handler找到不同的適配器處理器(HandlerAdapter)榨惠,處理后進(jìn)行視圖解析最后返回給用戶奋早。

HandlerMapping初始化

HandlerMapping

public interface HandlerMapping {
        //常量忽略
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

通過HandlerMapping可以獲取具體的HandlerExecutionChain(一個(gè)Handler的包裝類型)

public class HandlerExecutionChain {
    private final Object handler;
    private HandlerInterceptor[] interceptors;
    private List<HandlerInterceptor> interceptorList;
    private int interceptorIndex = -1;

    public HandlerExecutionChain(Object handler) {
        this(handler, (HandlerInterceptor[]) null);
    }

    public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
        if (handler instanceof HandlerExecutionChain) {
            HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
            this.handler = originalChain.getHandler();
            this.interceptorList = new ArrayList<HandlerInterceptor>();
            CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
            CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
        }
        else {
            this.handler = handler;
            this.interceptors = interceptors;
        }
    }

    public Object getHandler() {
        return this.handler;
    }
//其他代碼忽略

其中Handler為真正的處理器 (比如盛霎,如果你繼承了Controller,那么一個(gè)Controller就是一個(gè)Handler,如果你使用了注解,那么一個(gè)方法就是一個(gè)Handler)耽装。

那么其他的interceptors或者interceptorList則是Spring mvc的攔截器愤炸,這些攔截器可以在Handler進(jìn)行處理業(yè)務(wù)邏輯的前后,進(jìn)行前置處理或者后置處理掉奄。

所以HandlerExecutionChain是一個(gè)帶有攔截器的Handler包裝類规个。

initHandlerMappings

public class DispatcherServlet extends FrameworkServlet {
private boolean detectAllHandlerMappings = true;//是否查詢所有的HandlerMappings,如果此配置為false,那么必須找到名稱為handlerMapping的HandlerMapping.

  private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // 在上下文中查找所有的HandlerMapping
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // 對所有的HandlerMapping進(jìn)行排序
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            //忽略
        }

        //經(jīng)過前兩步凤薛,都沒有HandlerMappings 那么啟動(dòng)一個(gè)默認(rèn)的策略。
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            //忽略
        }
    }
}

也就是說我們可以注冊HandlerMapping,也可以使用默認(rèn)策略诞仓,這步理論上講缤苫,應(yīng)該是不會(huì)報(bào)錯(cuò)的。

當(dāng)我們啟動(dòng)spring boot的時(shí)候墅拭,我們看到如下:


spring默認(rèn)加載的HandlerMapping

經(jīng)過排序后活玲,整個(gè)HandlerMapping的順序如下:

  1. = {SimpleUrlHandlerMapping@7073}
  2. = {RequestMappingHandlerMapping@7063}
  3. = {BeanNameUrlHandlerMapping@7067}
  4. = {SimpleUrlHandlerMapping@7069}
  5. = {WebMvcConfigurationSupport$EmptyHandlerMapping@7065}
  6. = {WebMvcConfigurationSupport$EmptyHandlerMapping@7071}
  7. = {WebMvcAutoConfiguration$WelcomePageHandlerMapping@7075}

使用案例

    @Bean
    public SimpleUrlHandlerMapping mySimpleUrlHandlerMapping(){
        SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
        simpleUrlHandlerMapping.setUrlMap(ImmutableMap.of("/lrwin",new TestController()));
        simpleUrlHandlerMapping.setOrder(Integer.MIN_VALUE);
        return simpleUrlHandlerMapping;
    }
    

    public class TestController extends AbstractController{
        @Override
        protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {

            ModelAndView modelAndView = new ModelAndView();
            System.out.println("aaa");
            return modelAndView;
        }
    }

當(dāng)訪問http://localhost:8080/lrwin的時(shí)候,可以看到控制臺(tái)輸出了aaa.

SimpleUrlHandlerMapping的主要作用是可以給Controller取個(gè)訪問路徑別名谍婉,然后進(jìn)行訪問舒憾,可以找到相關(guān)聯(lián)的Controller。

    @Component("/lrwinx")
    public class TestController extends AbstractController{
        @Override
        protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {

            ModelAndView modelAndView = new ModelAndView();
            System.out.println("aaa");
            return modelAndView;
        }
    }

當(dāng)訪問http://localhost:8080/lrwinx的時(shí)候穗熬,同樣可以看到aaa

上邊使用的HandlerMapping為BeanNameUrlHandlerMapping镀迂,它可以通過bean的名稱來查詢響應(yīng)的Controller.

上邊所述的Controller都是處理器(Handler).

還記得HandlerExecutionChain是Handler的一種包裝類型吧。 HandlerMapping接口定義了返回這樣的類型唤蔗。

除了上述的兩個(gè)實(shí)例以外探遵,還有一種是注解的方式,注解方式中的每一個(gè)帶有@RequestMapping的方法都是一個(gè)Handler. 這個(gè)復(fù)雜度比較高措译,我們最后再說别凤。

類圖層次

HandlerMapping類圖結(jié)構(gòu)

AbstractHandlerMapping

AbstractHandlerMapping結(jié)構(gòu)圖

看代碼:

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
        @Override
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }

    protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
}

子類只需要告訴誰是Handler就可以,當(dāng)然Handler可以為處理器领虹,也可以是處理器的封裝類型HandlerExecutionChain规哪。

AbstractUrlHandlerMapping

根據(jù)URL查詢Handler關(guān)鍵方法

因?yàn)榇朔椒ㄊ歉鶕?jù)URL查詢Handler的類,所以它的重點(diǎn)方法是lookupHandler.

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        // handlerMap存儲(chǔ)了了urlPath對應(yīng)的Handler--是一個(gè)Map類型塌衰。此handlerMap在本類方法registerHandler中進(jìn)行注冊的诉稍。
        Object handler = this.handlerMap.get(urlPath);
        if (handler != null) {
            // 如果獲取到的Handler是一個(gè)String類型,那么就要考慮它有可能是一個(gè)beanName
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(handler, request);
                        //構(gòu)建一個(gè)帶有基礎(chǔ)信息的HandlerExecutionChain(Handler的封裝類型)
            return buildPathExposingHandler(handler, urlPath, urlPath, null);
        }
        // 如果不是直接匹配的最疆,那么是不是有可能是AntPathMatcher匹配的杯巨,當(dāng)然,AntPathMatcher這種匹配努酸,有可能會(huì)有多個(gè)服爷,所以使用了List進(jìn)行了臨時(shí)存儲(chǔ)
        List<String> matchingPatterns = new ArrayList<String>();
        for (String registeredPattern : this.handlerMap.keySet()) {
            if (getPathMatcher().match(registeredPattern, urlPath)) {
                matchingPatterns.add(registeredPattern);
            }
            else if (useTrailingSlashMatch()) {
                if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
                    matchingPatterns.add(registeredPattern +"/");
                }
            }
        }
//經(jīng)過AntPathMatcher排序方式,獲取一個(gè)最佳的匹配路徑(也就是匹配路徑List排序后的第一個(gè))
        String bestPatternMatch = null;
        Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
        if (!matchingPatterns.isEmpty()) {
            Collections.sort(matchingPatterns, patternComparator);
            if (logger.isDebugEnabled()) {
                logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
            }
            bestPatternMatch = matchingPatterns.get(0);
        }
//再通過最佳路徑去halerMap中去查找對應(yīng)的Handler
        if (bestPatternMatch != null) {
            handler = this.handlerMap.get(bestPatternMatch);
            if (handler == null) {
                Assert.isTrue(bestPatternMatch.endsWith("/"));
                handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
            }
            // Bean name or resolved handler?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(handler, request);
            String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);

            // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
            // for all of them
            Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
            for (String matchingPattern : matchingPatterns) {
                if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
                    Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                    Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
                    uriTemplateVariables.putAll(decodedVars);
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
            }
            return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
        }
        // No handler found...
        return null;
    }

為了更深入的了解获诈,我們關(guān)注一下排序算法:

Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
            Collections.sort(matchingPatterns, patternComparator);

getPathMatcher()是一個(gè)AntPathMatcher. 看一下getPatternComparator這個(gè)方法:

@Override
    public Comparator<String> getPatternComparator(String path) {
        return new AntPatternComparator(path);
    }

AntPatternComparator 會(huì)對符合ant表達(dá)式的url進(jìn)行排序仍源。
排序順序:

  1. if it's null or a capture all pattern (i.e. it is equal to "/**")
  2. if the other pattern is an actual match
  3. if it's a catch-all pattern (i.e. it ends with "**"
  4. if it's got more "*" than the other pattern
  5. if it's got more "{foo}" than the other pattern
  6. if it's shorter than the other pattern

再來看一下,如何注冊Handler到handlerMap中的舔涎。

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
//一群驗(yàn)證和閑雜邏輯
                this.handlerMap.put(urlPath, resolvedHandler);
}

這樣的話笼踩,我們得知,AbstractUrlHandlerMapping其實(shí)已經(jīng)完成了注冊Handler,和通過URL查找可用Handler的邏輯亡嫌,它的子類只需要在合適的時(shí)機(jī)嚎于,調(diào)用注冊方法就可以掘而。我們來看他的子類:SimpleUrlHandlerMapping

SimpleUrlHandlerMapping

public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
    private final Map<String, Object> urlMap = new LinkedHashMap<String, Object>();
  
       //可以使用 properties配置,然后添加到urlMap中
    public void setMappings(Properties mappings) {
        CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
    }
//設(shè)置urlMap中
    public void setUrlMap(Map<String, ?> urlMap) {
        this.urlMap.putAll(urlMap);
    }
    public Map<String, ?> getUrlMap() {
        return this.urlMap;
    }

//初始化時(shí)于购,注冊所有的Handler
    @Override
    public void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        registerHandlers(this.urlMap);
    }

//調(diào)用父類注冊方法registerHandler的邏輯
    protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
                String url = entry.getKey();
                Object handler = entry.getValue();
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);
            }
        }
    }
}

小結(jié)

  1. HandlerMapping

定義了需要返回handler的包裝類型HandlerExecutionChain的接口 : getHandler

2.AbstractHandlerMapping

封裝了getHandler,并且封裝了攔截器袍睡。子類需要提供一下Handler

protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

3.AbstractUrlHandlerMapping
實(shí)現(xiàn)了getHandlerInternal,提供了如何通過URL查找Handler的方法肋僧,核心邏輯方法:

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {

提供了注冊方法,子類需要調(diào)用注冊方法:

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException 

4.SimpleUrlHandlerMapping
僅僅是解析Properties或者M(jìn)ap數(shù)據(jù)女蜈,然后在初始化的時(shí)候調(diào)用父類的registerHandler進(jìn)行注冊。

牛逼的注解HanlderMapping

RequestMappingHandlerMapping類圖

AbstractHandlerMethodMapping

這是一個(gè)以方法為Handler的抽象類:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception

看一下HandlerMethod:

public class HandlerMethod {
    private final Object bean;//類對象
    private final Class<?> beanType;//對象的Class
    private final Method method;//具體方法
    private final Method bridgedMethod;//橋接方法(橋接方法以后再將)
    private final MethodParameter[] parameters;//方法參數(shù)
    private final HandlerMethod resolvedFromHandlerMethod;//其他Handler的方法
//...
}

這個(gè)HandlerMethod就是一個(gè)方法的描述類色瘩。

@Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        //...
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            //...
    }

核心方法lookupHandlerMethod:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
//找到根據(jù)url找到所有對應(yīng)的Match(這個(gè)對象是HandlerMethod的包裝類型伪窖,它包含HandlerMethod和他的mapping)
        List<Match> matches = new ArrayList<Match>();
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
//從Match集合中,找到最佳的一個(gè)
        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            Collections.sort(matches, comparator);
            if (logger.isTraceEnabled()) {
                logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
                        lookupPath + "] : " + matches);
            }
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                            request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
                }
            }
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

對于AbstractHandlerMethodMapping,有幾個(gè)概念:

mapping:處理方法的mapping(后邊會(huì)講到居兆,是一個(gè)RequestMappingInfo)

handler:處理器(只一個(gè)類)
method:執(zhí)行方法(類的執(zhí)行方法)

HandlerMethod: 可以理解成一個(gè)handler和method的一個(gè)封裝覆山。

Match: mapping和HandlerMethod的一個(gè)封裝。

1.根據(jù) url找到所有的mapping:
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
2.封裝成找到對應(yīng)的Match:
T match = getMatchingMapping(mapping, request);

protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);

這個(gè)要由子類實(shí)現(xiàn)
3.加入集合,然后找到最佳Match

RequestMappingInfoHandlerMapping

RequestMappingInfo:


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;
//....
}

這個(gè)RequestMappingInfo類泥栖,其實(shí)和@RequestMapping里的屬性是一一對應(yīng)的簇宽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吧享,隨后出現(xiàn)的幾起案子魏割,更是在濱河造成了極大的恐慌,老刑警劉巖钢颂,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钞它,死亡現(xiàn)場離奇詭異,居然都是意外死亡殊鞭,警方通過查閱死者的電腦和手機(jī)遭垛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來操灿,“玉大人锯仪,你說我怎么就攤上這事≈貉危” “怎么了庶喜?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長救鲤。 經(jīng)常有香客問我久窟,道長,這世上最難降的妖魔是什么蜒简? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任瘸羡,我火速辦了婚禮漩仙,結(jié)果婚禮上搓茬,老公的妹妹穿的比我還像新娘犹赖。我一直安慰自己,他們只是感情好卷仑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布峻村。 她就那樣靜靜地躺著,像睡著了一般锡凝。 火紅的嫁衣襯著肌膚如雪粘昨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天窜锯,我揣著相機(jī)與錄音张肾,去河邊找鬼。 笑死锚扎,一個(gè)胖子當(dāng)著我的面吹牛吞瞪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驾孔,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼芍秆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了翠勉?” 一聲冷哼從身側(cè)響起妖啥,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎对碌,沒想到半個(gè)月后荆虱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朽们,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年克伊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片华坦。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愿吹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惜姐,到底是詐尸還是另有隱情犁跪,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布歹袁,位于F島的核電站坷衍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏条舔。R本人自食惡果不足惜枫耳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望孟抗。 院中可真熱鬧迁杨,春花似錦钻心、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狐史,卻和暖如春痒给,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骏全。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工苍柏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人姜贡。 一個(gè)月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓序仙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鲁豪。 傳聞我的和親對象是個(gè)殘疾皇子潘悼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

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

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,394評論 1 92
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,806評論 6 342
  • 引言 一直以來都在使用Spring mvc爬橡,能夠熟練使用它的各種組件治唤。但是,它一直像個(gè)黑盒一樣糙申,我并不知道它內(nèi)部是...
    yoqu閱讀 908評論 0 24
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理宾添,服務(wù)發(fā)現(xiàn),斷路器柜裸,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • Spring MVC一缕陕、什么是 Spring MVCSpring MVC 屬于 SpringFrameWork 的...
    任任任任師艷閱讀 3,378評論 0 32