Spring Webflux --源碼閱讀之 handler包

Spring Webflux --handler

提供包括抽象基類在內(nèi)的HandlerMapping實(shí)現(xiàn)。

先扔一張整體的diagram類圖:

simoleUrlHandlerMapping.jpg

AbstractHandlerMapping

HandlerMapping實(shí)現(xiàn)的抽象基類。

接口

  • java.lang.Object
    • org.springframework.context.support.ApplicationObjectSupport
      • org.springframework.web.reactive.handler.AbstractHandlerMapping

實(shí)現(xiàn)了HandlerMapping, Ordered

    public abstract class AbstractHandlerMapping extends
    ApplicationObjectSupport implements HandlerMapping, Ordered {



private static final WebHandler REQUEST_HANDLED_HANDLER = exchange -> Mono.empty();


private int order = Integer.MAX_VALUE;  // default: same as non-Ordered

private final PathPatternParser patternParser;

private final UrlBasedCorsConfigurationSource globalCorsConfigSource;

private CorsProcessor corsProcessor = new DefaultCorsProcessor();


public AbstractHandlerMapping() {
      this.patternParser = new PathPatternParser();
      this.globalCorsConfigSource = new UrlBasedCorsConfigurationSource(this.patternParser);
}

查找給定請(qǐng)求的handler仰迁,如果找不到特定的請(qǐng)求,則返回一個(gè)空的Mono。這個(gè)方法被getHandler(org.springframework.web.server.ServerWebExchange)調(diào)用硬猫。

在CORS 預(yù)先請(qǐng)求中,該方法應(yīng)該返回一個(gè)匹配改执,而不是預(yù)先請(qǐng)求的請(qǐng)求啸蜜,而是基于URL路徑的預(yù)期實(shí)際請(qǐng)求,從“Access-Control-Request-Method”頭辈挂,以及“Access-Control-Request-Headers”頭的HTTP方法衬横,通過(guò) getcorsconfiguration獲得CORS配置來(lái)允許通過(guò),

如果匹配到一個(gè)handler终蒂,就返回Mono

protected abstract Mono<?> getHandlerInternal(ServerWebExchange exchange);

檢索給定handle的CORS配置蜂林。

@Nullable
protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
    if (handler instanceof CorsConfigurationSource) {
        return ((CorsConfigurationSource) handler).getCorsConfiguration(exchange);
    }
    return null;
}

抽象類實(shí)現(xiàn)的主要的具體方法,來(lái)獲得具體的Handle,實(shí)現(xiàn)了HandlerMapping中的getHandler拇泣,Mono<Object> getHandler(ServerWebExchange exchange);

@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
    return getHandlerInternal(exchange).map(handler -> {
        if (CorsUtils.isCorsRequest(exchange.getRequest())) {
            CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange);
            CorsConfiguration configB = getCorsConfiguration(handler, exchange);
            CorsConfiguration config = (configA != null ? configA.combine(configB) : configB);
            if (!getCorsProcessor().process(config, exchange) ||
                    CorsUtils.isPreFlightRequest(exchange.getRequest())) {
                return REQUEST_HANDLED_HANDLER;
            }
        }
        return handler;
    });
}

AbstractUrlHandlerMapping

基于URL映射的HandlerMapping實(shí)現(xiàn)的抽象基類噪叙。

接口

  • java.lang.Object
    • org.springframework.context.support.ApplicationObjectSupport
      • org.springframework.web.reactive.handler.AbstractHandlerMapping
        • org.springframework.web.reactive.handler.AbstractUrlHandlerMapping

支持直接匹配,例如注冊(cè)的“/ test”匹配“/ test”霉翔,以及各種ant樣式匹配睁蕾,例如, “/ test *”匹配“/ test”和“/ team”,“/ test / *”匹配“/ test”下的所有路徑子眶, 瀑凝。有關(guān)詳細(xì)信息,請(qǐng)參閱PathPattern javadoc臭杰。

將搜索所有路徑模式以查找當(dāng)前請(qǐng)求路徑的最具體匹配粤咪。最具體的模式定義為使用最少捕獲變量和通配符的最長(zhǎng)路徑模式。

private final Map<PathPattern, Object> handlerMap = new LinkedHashMap<>();

返回注冊(cè)路徑模式和handle的只讀視圖渴杆,這些注冊(cè)路徑模式和handle可能是一個(gè)實(shí)際的handle實(shí)例或延遲初始化handle的bean名稱寥枝。

public final Map<PathPattern, Object> getHandlerMap() {
    return Collections.unmodifiableMap(this.handlerMap);
}

我們可以再看到下面這兩個(gè)方法實(shí)現(xiàn)了handle的注冊(cè),會(huì)把所有的路徑映射将塑,和handle實(shí)例放在handlerMap中

protected void registerHandler(String[] urlPaths, String beanName)
        throws BeansException, IllegalStateException {

    Assert.notNull(urlPaths, "URL path array must not be null");
    for (String urlPath : urlPaths) {
        registerHandler(urlPath, beanName);
    }
}


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

    Assert.notNull(urlPath, "URL path must not be null");
    Assert.notNull(handler, "Handler object must not be null");
    Object resolvedHandler = handler;

    // Parse path pattern
    urlPath = prependLeadingSlash(urlPath);
    PathPattern pattern = getPathPatternParser().parse(urlPath);
    if (this.handlerMap.containsKey(pattern)) {
        Object existingHandler = this.handlerMap.get(pattern);
        if (existingHandler != null) {
            if (existingHandler != resolvedHandler) {
                throw new IllegalStateException(
                        "Cannot map " + getHandlerDescription(handler) + " to [" + urlPath + "]: " +
                        "there is already " + getHandlerDescription(existingHandler) + " mapped.");
            }
        }
    }

    // Eagerly resolve handler if referencing singleton via name.
    if (!this.lazyInitHandlers && handler instanceof String) {
        String handlerName = (String) handler;
        if (obtainApplicationContext().isSingleton(handlerName)) {
            resolvedHandler = obtainApplicationContext().getBean(handlerName);
        }
    }

    // Register resolved handler
    this.handlerMap.put(pattern, resolvedHandler);
    if (logger.isInfoEnabled()) {
        logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
    }
}

預(yù)處理映射的路徑脉顿,如果不以/開(kāi)頭就加上/

private static String prependLeadingSlash(String pattern) {
    if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
        return "/" + pattern;
    }
    else {
        return pattern;
    }
}

在這一步的時(shí)候開(kāi)始獲取內(nèi)部的handle蝌麸,查找給定請(qǐng)求的handle点寥,如果找不到特定的請(qǐng)求,則返回一個(gè)空的Mono来吩。

    @Override
public Mono<Object> getHandlerInternal(ServerWebExchange exchange) {
    PathContainer lookupPath = exchange.getRequest().getPath().pathWithinApplication();
    Object handler;
    try {
        handler = lookupHandler(lookupPath, exchange);
    }
    catch (Exception ex) {
        return Mono.error(ex);
    }

    if (handler != null && logger.isDebugEnabled()) {
        logger.debug("Mapping [" + lookupPath + "] to " + handler);
    }
    else if (handler == null && logger.isTraceEnabled()) {
        logger.trace("No handler mapping found for [" + lookupPath + "]");
    }

    return Mono.justOrEmpty(handler);
}

獲取到handle的類敢辩,先獲取請(qǐng)求的url地址,調(diào)用lookupHandler(lookupPath, exchange)去找這個(gè)handle弟疆。

@Nullable
protected Object lookupHandler(PathContainer lookupPath, ServerWebExchange exchange)
        throws Exception {

    return this.handlerMap.entrySet().stream()
            .filter(entry -> entry.getKey().matches(lookupPath))
            .sorted((entry1, entry2) ->
                    PathPattern.SPECIFICITY_COMPARATOR.compare(entry1.getKey(), entry2.getKey()))
            .findFirst()
            .map(entry -> {
                PathPattern pattern = entry.getKey();
                if (logger.isDebugEnabled()) {
                    logger.debug("Matching pattern for request [" + lookupPath + "] is " + pattern);
                }
                PathContainer pathWithinMapping = pattern.extractPathWithinPattern(lookupPath);
                return handleMatch(entry.getValue(), pattern, pathWithinMapping, exchange);
            })
            .orElse(null);
}

在這里又調(diào)用了handleMatch(entry.getValue(), pattern, pathWithinMapping, exchange)來(lái)匹配handle戚长,驗(yàn)證過(guò)后,然后設(shè)置到ServerWebExchange中最后返回怠苔。

private Object handleMatch(Object handler, PathPattern bestMatch, PathContainer pathWithinMapping,
        ServerWebExchange exchange) {

    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    validateHandler(handler, exchange);

    exchange.getAttributes().put(BEST_MATCHING_HANDLER_ATTRIBUTE, handler);
    exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatch);
    exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);

    return handler;
}

SimpleUrlHandlerMapping

HandlerMapping的實(shí)現(xiàn)同廉,來(lái)把url請(qǐng)求映射到對(duì)應(yīng)的request handler的bean

支持映射到bean實(shí)例和映射到bean名稱;非單例的handler需要映射到bean名稱。

“urlMap”屬性適合用bean實(shí)例填充處理程序映射柑司∑刃ぃ可以通過(guò)java.util.Properties類接受的形式,通過(guò)“映射”屬性設(shè)置映射到bean名稱攒驰,如下所示:

/welcome.html=ticketController

/show.html=ticketController

語(yǔ)法是PATH = HANDLER_BEAN_NAME蟆湖。如果路徑不是以斜杠開(kāi)始的,則給它自動(dòng)補(bǔ)充一個(gè)斜杠玻粪。

支持直接匹配隅津,例如注冊(cè)的“/ test”匹配“/ test”,以及各種ant樣式匹配劲室,例如伦仍, “/ test *”匹配“/ test”和“/ team”,“/ test / *”匹配“/ test”下的所有路徑很洋, 充蓝。有關(guān)詳細(xì)信息,請(qǐng)參閱PathPattern javadoc蹲缠。

接口

  • java.lang.Object
    • org.springframework.context.support.ApplicationObjectSupport
      • org.springframework.web.reactive.handler.AbstractHandlerMapping
        • org.springframework.web.reactive.handler.AbstractUrlHandlerMapping
          • org.springframework.web.reactive.handler.SimpleUrlHandlerMapping
private final Map<String, Object> urlMap = new LinkedHashMap<>();


程序啟動(dòng)遍歷的時(shí)候把加載到的所有映射路徑棺克,和handle設(shè)置到urlMap
public void setUrlMap(Map<String, ?> urlMap) {
    this.urlMap.putAll(urlMap);
}

獲得所有的urlMap悠垛,允許urlMap訪問(wèn)URL路徑映射,可以添加或覆蓋特定條目娜谊。
public Map<String, ?> getUrlMap() {
    return this.urlMap;
}


初始化程序上下文确买,除了父類的初始化,還調(diào)用了registerHandler
@Override
public void initApplicationContext() throws BeansException {
    super.initApplicationContext();
    registerHandlers(this.urlMap);
}

開(kāi)始注冊(cè)handler纱皆,注冊(cè)u(píng)rlMap中為相應(yīng)路徑指定的所有的handler湾趾。
如果handler不能注冊(cè),拋出 BeansException
如果有注冊(cè)的handler有沖突派草,比如兩個(gè)相同的搀缠,拋出java.lang.IllegalStateException

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);
        }
    }
}

這里調(diào)用的registerHandler(url, handler)就是剛剛抽象類AbstractUrlHandlerMapping中的registerHandler方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市近迁,隨后出現(xiàn)的幾起案子艺普,更是在濱河造成了極大的恐慌,老刑警劉巖鉴竭,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歧譬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡搏存,警方通過(guò)查閱死者的電腦和手機(jī)瑰步,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)璧眠,“玉大人缩焦,你說(shuō)我怎么就攤上這事≡鹁玻” “怎么了袁滥?”我有些...
    開(kāi)封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)泰演。 經(jīng)常有香客問(wèn)我呻拌,道長(zhǎng),這世上最難降的妖魔是什么睦焕? 我笑而不...
    開(kāi)封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任藐握,我火速辦了婚禮,結(jié)果婚禮上垃喊,老公的妹妹穿的比我還像新娘猾普。我一直安慰自己,他們只是感情好本谜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布初家。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溜在。 梳的紋絲不亂的頭發(fā)上陌知,一...
    開(kāi)封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音掖肋,去河邊找鬼仆葡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛志笼,可吹牛的內(nèi)容都是我干的沿盅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纫溃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腰涧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起紊浩,我...
    開(kāi)封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤窖铡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后郎楼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體万伤,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窒悔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年呜袁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片简珠。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阶界,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出聋庵,到底是詐尸還是另有隱情膘融,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布祭玉,位于F島的核電站氧映,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脱货。R本人自食惡果不足惜岛都,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望振峻。 院中可真熱鬧臼疫,春花似錦、人聲如沸扣孟。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鸽斟,卻和暖如春拔创,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背富蓄。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工伏蚊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人格粪。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓躏吊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親帐萎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子比伏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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