SpringMVC源碼分析--HandlerMappings

之前分析過SpringMVC中的DispatcherServlet,分析了SpringMVC處理請求的過程砸喻。但忽略了一些DispatcherServlet協(xié)助請求處理的組件川抡,例如SpringMVC中的HandlerMappingHandlerAdapter返奉、ViewResolvers等等变逃。

HandlerMappings

HandlerMappingsDispathServlet中主要作用是為請求的urlpath匹配對應(yīng)的Controller必逆,建立一個映射關(guān)系,根據(jù)請求查找Handler揽乱、Interceptor名眉。HandlerMappings將請求傳遞到HandlerExecutionChain上,HandlerExecutionChain包含了一個能夠處理該請求的處理器凰棉,還可以包含攔截改請求的攔截器损拢。

在沒有處理器映射相關(guān)配置情況下,DispatcherServlet會為你創(chuàng)建一個BeanNameUrlHandlerMapping作為默認映射的配置撒犀。在DispatchServlet.properties文件中對于HandlerMapping的默認配置是:

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

HandlerMapping的配置策略一般分為配置式BeanNameUrlHandlerMapping和注解式DefaultAnnotationHandlerMapping探橱。不過DefaultAnnotationHandlerMapping已經(jīng)被放棄了,取代它的是RequestMappingHandlerMapping绘证,不知道為啥SpringMVC這個默認配置尚未做修改。

AbstractHandlerMapping

AbstractHandlerMappingHandlerMapping的抽象實現(xiàn)哗讥,是所有HandlerMapping實現(xiàn)類的父類嚷那。

AbstractHandlerMapping的作用是是為了初始化InterceptorsAbstractHandlerMapping重寫了WebApplicationObjectSupportinitApplicationContext方法杆煞。

    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.adaptedInterceptors);
        initInterceptors();
    }
  • extendInterceptors方法魏宽,Springmvc并沒有做出具體實現(xiàn),這里留下一個拓展决乎,子類可以重寫這個模板方法队询,為子類添加或者修改Interceptors

  • detectMappedInterceptors方法將SpringMVC容器中所有MappedInterceptor類的bean添加到adaptedInterceptors中构诚。

  • 最后調(diào)用initInterceptors初始化攔截器蚌斩。遍歷interceptorsWebRequestInterceptorHandlerInterceptor類型的攔截器添加到adaptedInterceptors中。

HandlerMapping通過getHandler方法來獲取請求的處理器Handler和攔截器Interceptor范嘱。在getHandlerExecutionChain方法中將遍歷之前初始化的adaptedInterceptors送膳,為當前的請求選擇對應(yīng)的MappedInterceptorsadaptedInterceptors

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping繼承于AbstractHandlerMapping丑蛤,它是通過URL來匹配具體的Handler叠聋。AbstractUrlHandlerMapping維護一個handlerMap來存儲UrlHandler的映射關(guān)系。

AbstractUrlHandlerMapping重寫了AbstractHandlerMapping類中的getHandlerInternal方法受裹。HandlerMapping通過getHandler方法碌补,就會調(diào)用這里的getHandlerInternal方法來獲取HandlergetHandlerInternal方法中關(guān)鍵調(diào)用lookupHandler方法去獲取handler

    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        // Direct match?
        Object handler = this.handlerMap.get(urlPath);
        if (handler != null) {
            // Bean name or resolved handler?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(handler, request);
            return buildPathExposingHandler(handler, urlPath, urlPath, null);
        }

        // Pattern match?
        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 +"/");
                }
            }
        }

        String bestMatch = 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);
            }
            bestMatch = matchingPatterns.get(0);
        }
        if (bestMatch != null) {
            handler = this.handlerMap.get(bestMatch);
            if (handler == null) {
                if (bestMatch.endsWith("/")) {
                    handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
                }
                if (handler == null) {
                    throw new IllegalStateException(
                            "Could not find handler for best pattern match [" + bestMatch + "]");
                }
            }
            // Bean name or resolved handler?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(handler, request);
            String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, 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(bestMatch, 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, bestMatch, pathWithinMapping, uriTemplateVariables);
        }

        // No handler found...
        return null;
    }
  • 首先調(diào)用lookupHandler方法來獲取handler厦章。在lookupHandler方法中镇匀,先通過URLhandlerMap查找是否有合適的handler
  • 如果沒有獲取到handler闷袒,遍歷handlerMap利用正則匹配的方法坑律,找到符合要求的handlers(有可能是多個)。
  • 正則匹配是采用Ant風格囊骤,將會通過排序篩選出一個匹配程度最高的Handler晃择。
  • 最后調(diào)用buildPathExposingHandler方法構(gòu)建一個handler,添加PathExposingHandlerInterceptorUriTemplateVariablesHandlerInterceptor兩個攔截器并返回。

上面介紹獲取handler的過程中也物,會先從handlerMap查找宫屠。下面看一下handlerMap是如何初始化的。AbstractUrlHandlerMapping是通過registerHandler初始化handlerMap的滑蚯。AbstractUrlHandlerMapping共有兩個registerHandler方法浪蹂。分別是注冊多個url到一個handler和注冊一個url到一個handler。首先判斷handlerMap是否有此handler告材。如果存在的話坤次,判斷是否一致,不一致則拋出異常斥赋,如果不存在的話缰猴,如果url//*疤剑,則滑绒,返回root handlerdefault handler隘膘,如果不是將添加到handlerMap中疑故。

SimpleUrlHandlerMapping

SimpleUrlHandlerMapping繼承于AbstractUrlHandlerMappingSimpleUrlHandlerMapping重寫了父類AbstractHandlerMapping中的初始化方法initApplicationContext弯菊。在initApplicationContext方法中調(diào)用registerHandlers方法纵势。

    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();
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);
            }
        }
    }

判斷是url是否以/開頭,如果不是管钳,默認補齊/,確保所有的url都是以/開頭吨悍,然后依次調(diào)用父類的registerHandler方法注冊到AbstractUrlHandlerMapping中的handlerMap

在使用SimpleUrlHandlerMapping時蹋嵌,需要在注冊的時候配置其urlmap否則會拋異常育瓜。

AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping類繼承于AbstractUrlHandlerMapping類,重寫了initApplicationContext方法栽烂,在initApplicationContext方法中調(diào)用了detectHandlers方法躏仇。

    protected void detectHandlers() throws BeansException {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlersInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        // Take any bean name that we can determine URLs for.
        for (String beanName : beanNames) {
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                // URL paths found: Let's consider it a handler.
                registerHandler(urls, beanName);
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
                }
            }
        }
    }

獲取所有容器的beanNames恋脚,遍歷所有的beanName,調(diào)用determineUrlsForHandler方法解析url焰手,這里的determineUrlsForHandler也是運用了模板方法設(shè)計模式糟描,具體的實現(xiàn)在其子類中,如果解析到子類书妻,將注冊到父類的handlerMap中船响。

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping類的類圖大致如下:

BeanNameUrlHandlerMapping類繼承于AbstractDetectingUrlHandlerMapping類。重寫了父類中的determineUrlsForHandler方法躲履。

    protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<String>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = getApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }

其通過beanName解析Url規(guī)則也很簡單见间,判斷beanName是否以/開頭。

BeanNameUrlHandlerMappingSpringMVC的默認映射配置工猜。

AbstractHandlerMethodMapping

通常我們也習慣于用@Controller米诉、@Re questMapping來定義HandlerAbstractHandlerMethodMapping可以將method作為Handler來使用篷帅。

AbstractHandlerMethodMapping實現(xiàn)了InitializingBean接口史侣,實現(xiàn)了afterPropertiesSet方法。當容器啟動的時候會調(diào)用initHandlerMethods注冊委托handler中的方法魏身。


    public void afterPropertiesSet() {
        initHandlerMethods();
    }
    
    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

initHandlerMethods方法中惊橱,做了以下工作:

  • 首先通過BeanFactoryUtils掃描應(yīng)用上下文,獲取所有的bean箭昵。
  • 遍歷所有的beanName税朴,調(diào)用isHandler方法判斷是目標bean是否包含@Controller@RequestMapping注解。
  • 對于帶有@Controller@RequestMapping注解的類宙枷,調(diào)用detectHandlerMethods委托處理,獲取所有的method,并調(diào)用registerHandlerMethod注冊所有的方法茧跋。

detectHandlerMethods方法負責將Handler保存到Map中慰丛。

    protected void detectHandlerMethods(final Object handler) {
       //  獲取handler的類型
        Class<?> handlerType = (handler instanceof String ?
                getApplicationContext().getType((String) handler) : handler.getClass());
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                new MethodIntrospector.MetadataLookup<T>() {
                    @Override
                    public T inspect(Method method) {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    }
                });

        if (logger.isDebugEnabled()) {
            logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
        }
        for (Map.Entry<Method, T> entry : methods.entrySet()) {
            Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
            T mapping = entry.getValue();
            registerHandlerMethod(handler, invocableMethod, mapping);
        }
    }
    
    

selectMethods方法中重寫了MetadataLookup中的inspect方法,inspect方法中調(diào)用了子類RequestMappingHandlerMapping實現(xiàn)了getMappingForMethod模板方法瘾杭,用于構(gòu)建RequestMappingInfo诅病。

public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
        final Map<Method, T> methodMap = new LinkedHashMap<Method, T>();
        Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
        Class<?> specificHandlerType = null;

        if (!Proxy.isProxyClass(targetType)) {
            handlerTypes.add(targetType);
            specificHandlerType = targetType;
        }
        handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));

        for (Class<?> currentHandlerType : handlerTypes) {
            final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);

            ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
                @Override
                public void doWith(Method method) {
                    Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                    T result = metadataLookup.inspect(specificMethod);
                    if (result != null) {
                        Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                        if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                            methodMap.put(specificMethod, result);
                        }
                    }
                }
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }

        return methodMap;
    }

selectMethods通過反射獲取所有的方法,重寫了doWith方法粥烁,將handler中的method和請求對應(yīng)的RequestMappingInfo保存到methodMap中贤笆。

最終detectHandlerMethods將遍歷這個methodMap,調(diào)用registerHandlerMethod注冊HandlerMethodMappingRegistry讨阻。

AbstractHandlerMethodMapping類中芥永,有個內(nèi)部類MappingRegistry,用來存儲mappinghandler methods注冊關(guān)系钝吮,并提供了并發(fā)訪問方法埋涧。

AbstractHandlerMethodMapping通過getHandlerInternal來為一個請求選擇對應(yīng)的handler板辽。

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
       // 根據(jù)request獲取對應(yīng)的urlpath
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        if (logger.isDebugEnabled()) {
            logger.debug("Looking up handler method for path " + lookupPath);
        }
        // 獲取讀鎖
        this.mappingRegistry.acquireReadLock();
        try {
          // 調(diào)用lookupHandlerMethod方法獲取請求對應(yīng)的HandlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            if (logger.isDebugEnabled()) {
                if (handlerMethod != null) {
                    logger.debug("Returning handler method [" + handlerMethod + "]");
                }
                else {
                    logger.debug("Did not find handler method for [" + lookupPath + "]");
                }
            }
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

lookupHandlerMethod的具體實現(xiàn)如下:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<Match>();
        // 通過lookupPath獲取所有匹配到的path
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
           // 將匹配條件添加到matches
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            // 如果沒有匹配條件,將所有的匹配條件都加入matches
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }

        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 + "}");
                }
            }
            // 將lookupPath設(shè)為請求request的PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE屬性
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

整個過程以Match作為載體棘催,Match是個內(nèi)部類劲弦,封裝了匹配條件和handlerMethod兩個屬性,默認的實現(xiàn)是將lookupPath設(shè)置為請求的屬性醇坝。

總結(jié)

本文從源碼角度上分析了HandlerMapping的各種實現(xiàn)邑跪。主要功能是為請求找到合適的handlerinterceptors,并組合成HandlerExecutionChain呼猪。查找handler的過程通過getHandlerInternal方法實現(xiàn)画畅,每個子類都其不同的實現(xiàn)。

所有的HandlerMapping的實現(xiàn)都繼承于AbstarctHandlerMapping郑叠,AbstarctHandlerMapping主要作用是完成攔截器的初始化工作夜赵。而通過AbstarctHandlerMapping又衍生出兩個系列,AbstractUrlHandlerMappingAbstractHandlerMethodMapping乡革。

AbstractUrlHandlerMapping也有很多子類的實現(xiàn)寇僧,如SimpleUrlHandlerMappingAbstractDetectingUrlHandlerMapping沸版∴铱總體來說,AbstractUrlHandlerMapping需要用到一個保存urlhandler的對應(yīng)關(guān)系的map视粮,map的初始化工作由子類實現(xiàn)细办。不同的子類會有自己的策略,可以在配置文件中注冊蕾殴,也可以在spring容器中找笑撞。

AbstractHandlerMethodMapping系列則通常用于注解的方法,解析包含@Controller或者@RequestMapping注解的類钓觉,建立urlmethod的直接對應(yīng)關(guān)系茴肥,這也是目前使用最多的一種方式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荡灾,一起剝皮案震驚了整個濱河市瓤狐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌批幌,老刑警劉巖础锐,帶你破解...
    沈念sama閱讀 211,496評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異荧缘,居然都是意外死亡皆警,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評論 3 385
  • 文/潘曉璐 我一進店門截粗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耀怜,“玉大人恢着,你說我怎么就攤上這事〔破疲” “怎么了掰派?”我有些...
    開封第一講書人閱讀 157,091評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長左痢。 經(jīng)常有香客問我靡羡,道長,這世上最難降的妖魔是什么俊性? 我笑而不...
    開封第一講書人閱讀 56,458評論 1 283
  • 正文 為了忘掉前任略步,我火速辦了婚禮,結(jié)果婚禮上定页,老公的妹妹穿的比我還像新娘趟薄。我一直安慰自己,他們只是感情好典徊,可當我...
    茶點故事閱讀 65,542評論 6 385
  • 文/花漫 我一把揭開白布杭煎。 她就那樣靜靜地躺著,像睡著了一般卒落。 火紅的嫁衣襯著肌膚如雪羡铲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,802評論 1 290
  • 那天儡毕,我揣著相機與錄音也切,去河邊找鬼。 笑死腰湾,一個胖子當著我的面吹牛雷恃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播费坊,決...
    沈念sama閱讀 38,945評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼倒槐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了葵萎?” 一聲冷哼從身側(cè)響起导犹,我...
    開封第一講書人閱讀 37,709評論 0 266
  • 序言:老撾萬榮一對情侶失蹤唱凯,失蹤者是張志新(化名)和其女友劉穎羡忘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磕昼,經(jīng)...
    沈念sama閱讀 44,158評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡卷雕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,502評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了票从。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漫雕。...
    茶點故事閱讀 38,637評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡滨嘱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浸间,到底是詐尸還是另有隱情太雨,我是刑警寧澤,帶...
    沈念sama閱讀 34,300評論 4 329
  • 正文 年R本政府宣布魁蒜,位于F島的核電站囊扳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏兜看。R本人自食惡果不足惜锥咸,卻給世界環(huán)境...
    茶點故事閱讀 39,911評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望细移。 院中可真熱鬧搏予,春花似錦、人聲如沸弧轧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劣针。三九已至校镐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捺典,已是汗流浹背鸟廓。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留襟己,地道東北人引谜。 一個月前我還...
    沈念sama閱讀 46,344評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像擎浴,于是被迫代替她去往敵國和親员咽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,500評論 2 348

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

  • 你要知道的SpringMVC DispatcherServlet執(zhí)行流程及源碼分析都在這里 轉(zhuǎn)載請注明出處 htt...
    WWWWDotPNG閱讀 10,251評論 2 25
  • HandlerMapping的類繼承圖如下: 其中有一個DefaultAnnotationHanlerMappin...
    宙斯是只貓閱讀 3,355評論 0 7
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理贮预,服務(wù)發(fā)現(xiàn)贝室,斷路器,智...
    卡卡羅2017閱讀 134,631評論 18 139
  • 引言 一直以來都在使用Spring mvc仿吞,能夠熟練使用它的各種組件滑频。但是,它一直像個黑盒一樣唤冈,我并不知道它內(nèi)部是...
    yoqu閱讀 905評論 0 24
  • 不見 有很多的話想說峡迷,心中的草稿已經(jīng)滿了 見 卻發(fā)現(xiàn)我根本沒有草稿箱。我慢慢的喜歡上她了,沒有理由绘搞,當?shù)谝惶炜匆?..
    南喬枝010121閱讀 141評論 0 0