歡迎光臨
每天分享高質量文章

Spring Webflux —— 原始碼閱讀之 handler 包

點選上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 


摘要: 原創出處 https://www.jianshu.com/p/23ee4b77c160 「一顆懶能」歡迎轉載,保留摘要,謝謝!

  • AbstractHandlerMapping

  • 介面


提供包括抽象基類在內的HandlerMapping實現。

先扔一張整體的diagram類圖:

img

simoleUrlHandlerMapping.jpg

AbstractHandlerMapping

HandlerMapping實現的抽象基類。

介面

  • java.lang.Object

  • org.springframework.context.support.ApplicationObjectSupport

    • org.springframework.web.reactive.handler.AbstractHandlerMapping

實現了HandlerMapping, Ordered

    public abstract class AbstractHandlerMapping extends
    ApplicationObjectSupport implements HandlerMappingOrdered 
{



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

查詢給定請求的handler,如果找不到特定的請求,則傳回一個空的Mono。這個方法被getHandler(org.springframework.web.server.ServerWebExchange)呼叫。

在CORS 預先請求中,該方法應該傳回一個匹配,而不是預先請求的請求,而是基於URL路徑的預期實際請求,從“Access-Control-Request-Method”頭,以及“Access-Control-Request-Headers”頭的HTTP方法,透過 getcorsconfiguration獲得CORS配置來允許透過,

如果匹配到一個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;
}

抽象類實現的主要的具體方法,來獲得具體的Handle,實現了HandlerMapping中的getHandler,MonogetHandler(ServerWebExchange exchange);

@Override
public Mono 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實現的抽象基類。

介面

  • java.lang.Object

  • org.springframework.context.support.ApplicationObjectSupport

    • org.springframework.web.reactive.handler.AbstractHandlerMapping

    • org.springframework.web.reactive.handler.AbstractUrlHandlerMapping

支援直接匹配,例如註冊的“/ test”匹配“/ test”,以及各種ant樣式匹配,例如, “/ test *”匹配“/ test”和“/ team”,“/ test / *”匹配“/ test”下的所有路徑, 。有關詳細資訊,請參閱PathPattern javadoc。

將搜尋所有路徑樣式以查詢當前請求路徑的最具體匹配。最具體的樣式定義為使用最少捕獲變數和萬用字元的最長路徑樣式。

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

傳回註冊路徑樣式和handle的只讀檢視,這些註冊路徑樣式和handle可能是一個實際的handle實體或延遲初始化handle的bean名稱。

public final Map getHandlerMap() {
    return Collections.unmodifiableMap(this.handlerMap);
}

我們可以再看到下麵這兩個方法實現了handle的註冊,會把所有的路徑對映,和handle實體放在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));
    }
}

預處理對映的路徑,如果不以/開頭就加上/

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

在這一步的時候開始獲取內部的handle,查詢給定請求的handle,如果找不到特定的請求,則傳回一個空的Mono。

    @Override
public Mono 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的類,先獲取請求的url地址,呼叫lookupHandler(lookupPath, exchange)去找這個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);
}

在這裡又呼叫了handleMatch(entry.getValue(), pattern, pathWithinMapping, exchange)來匹配handle,驗證過後,然後設定到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的實現,來把url請求對映到對應的request handler的bean

支援對映到bean實體和對映到bean名稱;非單例的handler需要對映到bean名稱。

“urlMap”屬性適合用bean實體填充處理程式對映。可以透過java.util.Properties類接受的形式,透過“對映”屬性設定對映到bean名稱,如下所示:

/welcome.html=ticketController

/show.html=ticketController

語法是PATH = HANDLER_BEAN_NAME。如果路徑不是以斜槓開始的,則給它自動補充一個斜槓。

支援直接匹配,例如註冊的“/ test”匹配“/ test”,以及各種ant樣式匹配,例如, “/ test *”匹配“/ test”和“/ team”,“/ test / *”匹配“/ test”下的所有路徑, 。有關詳細資訊,請參閱PathPattern javadoc。

介面

  • java.lang.Object

  • org.springframework.context.support.ApplicationObjectSupport

    • org.springframework.web.reactive.handler.SimpleUrlHandlerMapping

    • org.springframework.web.reactive.handler.AbstractHandlerMapping

    • org.springframework.web.reactive.handler.AbstractUrlHandlerMapping

private final Map urlMap = new LinkedHashMap<>();


程式啟動遍歷的時候把載入到的所有對映路徑,和handle設定到urlMap
public void setUrlMap(Map urlMap) 
{
    this.urlMap.putAll(urlMap);
}

獲得所有的urlMap,允許urlMap訪問URL路徑對映,可以新增或改寫特定條目。
public Map getUrlMap() {
    return this.urlMap;
}


初始化程式背景關係,除了父類的初始化,還呼叫了registerHandler
@Override
public void initApplicationContext() throws BeansException {
    super.initApplicationContext();
    registerHandlers(this.urlMap);
}

開始註冊handler,註冊urlMap中為相應路徑指定的所有的handler。
如果handler不能註冊,丟擲 BeansException
如果有註冊的handler有衝突,比如兩個相同的,丟擲java.lang.IllegalStateException

protected void registerHandlers(Map urlMap) throws BeansException {
    if (urlMap.isEmpty()) {
        logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
    }
    else {
        for (Map.Entry 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);
        }
    }
}

這裡呼叫的registerHandler(url, handler)就是剛剛抽象類AbstractUrlHandlerMapping中的registerHandler方法

666. 彩蛋

如果你對 Spring Webflux 感興趣,歡迎加入我的知識星球一起交流。




如果你對 Dubbo 感興趣,歡迎加入我的知識星球一起交流。

知識星球

目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:

01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽

05. 拓展機制 SPI

06. 執行緒池

07. 服務暴露 Export

08. 服務取用 Refer

09. 註冊中心 Registry

10. 動態編譯 Compile

11. 動態代理 Proxy

12. 服務呼叫 Invoke

13. 呼叫特性 

14. 過濾器 Filter

15. NIO 伺服器

16. P2P 伺服器

17. HTTP 伺服器

18. 序列化 Serialization

19. 叢集容錯 Cluster

20. 優雅停機

21. 日誌適配

22. 狀態檢查

23. 監控中心 Monitor

24. 管理中心 Admin

25. 運維命令 QOS

26. 鏈路追蹤 Tracing


一共 60 篇++

原始碼不易↓↓↓

點贊支援老艿艿↓↓

贊(0)

分享創造快樂