Spring Flux中Request與HandlerMapping關係的形成過程

一、前言

Spring Flux中的核心DispatcherHandler的處理過程分爲三步,其中首步就是通過HandlerMapping接口查找Request所對應的Handler。本文就是通過閱讀源碼的方式,分析一下HandlerMapping接口的實現者之一——RequestMappingHandlerMapping類,用於處理基於註解的路由策略,把所有用@Controller和@RequestMapping標記的類中的Handler識別出來,以便DispatcherHandler調用的。

HandlerMapping接口的另外兩種實現類:1、RouterFunctionMapping用於函數式端點的路由;2、SimpleUrlHandlerMapping用於顯式註冊的URL模式與WebHandler配對。

<!-- more -->

文章系列

二、對基於註解的路由控制器的抽象

Spring中基於註解的控制器的使用方法大致如下:

@Controller
public class MyHandler{
    @RequestMapping("/")
    public String handlerMethod(){

    }
}

在Spring WebFlux中,對上述使用方式進行了三層抽象模型。

  1. Mapping

    • 用戶定義的基於annotation的映射關係
    • 該抽象對應的類是:org.springframework.web.reactive.result.method.RequestMappingInfo
    • 比如上述例子中的 @RequestMapping("/")所代表的映射關係
  2. Handler

    • 代表控制器的類
    • 該抽象對應的類是:java.lang.Class
    • 比如上述例子中的MyHandler類
  3. Method

    • 具體處理映射的方法
    • 該抽象對應的類是:java.lang.reflect.Method
    • 比如上述例子中的String handlerMethod()方法

基於上述三層抽象模型,進而可以作一些組合。

  1. HandlerMethod

    • Handler與Method的結合體,Handler(類)與Method(方法)搭配後就成爲一個可執行的單元了
  2. Mapping vs HandlerMethod

    • 把Mapping與HandlerMethod作爲字典存起來,就可以根據請求中的關鍵信息(路徑、頭信息等)來匹配到Mapping,再根據Mapping找到HandlerMethod,然後執行HandlerMethod,並傳遞隨請求而來的參數。

理解了這個抽象模型後,接下來分析源碼來理解Spring WebFlux如何處理請求與Handler之間的Mapping關係時,就非常容易了。

HandlerMapping接口及其各實現類負責上述模型的構建與運作。

三、HandlerMapping接口實現的設計模式

HandlerMapping接口實現,採用了"模版方法"這種設計模式。

1層:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware

  ^  
  |  

2層:AbstractHandlerMethodMapping implements InitializingBean

  ^  
  |  

3層:RequestMappingInfoHandlerMapping

  ^  
  |  

4層:RequestMappingHandlerMapping implements EmbeddedValueResolverAware

下面對各層的職責作簡要說明:

  • 第1層主要實現了對外提供模型的接口

    • 即重載了HandlerMapping接口的"Mono<Object> getHandler(ServerWebExchange exchange) "方法,並定義了骨架代碼。
  • 第2層有兩個責任 —— 解析用戶定義的HandlerMethod + 實現對外提供模型接口實現所需的抽象方法

    • 通過實現了InitializingBean接口的"void afterPropertiesSet()"方法,解析用戶定義的Handler和Method。
    • 實現第1層對外提供模型接口實現所需的抽象方法:"Mono<?> getHandlerInternal(ServerWebExchange exchange)"
  • 第3層提供根據請求匹配Mapping模型實例的方法
  • 第4層實現一些高層次用到的抽象方法來創建具體的模型實例。

小結一下,就是HandlerMapping接口及其實現類,把用戶定義的各Controller等,抽象爲上述的Mapping、Handler及Method模型,並將Mapping與HandlerMethod作爲字典關係存起來,還提供通過匹配請求來獲得HandlerMethod的公共方法。

接下來的章節,將先分析解析用戶定義的模型並緩存模型的過程,然後再分析一下匹配請求來獲得HandlerMethod的公共方法的過程。

四、解析用戶定義的模型並緩存模型的過程

4-1、實現InitializingBean接口

第2層AbstractHandlerMethodMapping抽象類中的一個重要方法——實現了InitializingBean接口的"void afterPropertiesSet()"方法,爲Spring WebFlux帶來了解析用戶定義的模型並緩存模型的機會 —— Spring容器初初始化完成該類的具體類的Bean後,將會回調這個方法。
在該方法中,實現獲取用戶定義的Handler、Method、Mapping以及緩存Mapping與HandlerMethod映射關係的功能。

@Override
public void afterPropertiesSet() {

    initHandlerMethods();
    
    // Total includes detected mappings + explicit registrations via registerMapping..
    ...
}

4-2、找到用戶定義的Handler

afterPropertiesSet方法中主要是調用了void initHandlerMethods()方法,具體如下:

protected void initHandlerMethods() {
    //獲取Spring容器中所有Bean名字
    String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);

    for (String beanName : beanNames) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                //獲取Bean的類型
                beanType = obtainApplicationContext().getType(beanName);
            }
            catch (Throwable ex) {
                // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                if (logger.isTraceEnabled()) {
                    logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
                }
            }
            
            //如果獲取到類型,並且類型是Handler,則繼續加載Handler方法。
            if (beanType != null && isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }
    }
    
    //初始化後收尾工作
    handlerMethodsInitialized(getHandlerMethods());
}

這兒首先獲取Spring容器中所有Bean名字,然後循環處理每一個Bean。如果Bean名稱不是以SCOPED_TARGET_NAME_PREFIX常量開頭,則獲取Bean的類型。如果獲取到類型,並且類型是Handler,則繼續加載Handler方法。

isHandler(beanType)調用,檢查Bean的類型是否符合handler定義。
AbstractHandlerMethodMapping抽象類中定義的抽象方法"boolean isHandler(Class<?> beanType)",是由RequestMappingHandlerMapping類實現的。具體實現代碼如下:

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

不難看出,對於RequestMappingHandlerMapping這個實現類來說,只有擁有@Controller或者@RequestMapping註解的類,纔是Handler。(言下之意對於其他實現類來說Handler的定義不同)。

具體handler的定義,在HandlerMapping各實現類來說是不同的,這也是isHandler抽象方法由具體實現類來實現的原因。

4-3、發現Handler的Method

接下來我們要重點看一下"detectHandlerMethods(beanName);"這個方法調用。

protected void detectHandlerMethods(final Object handler) {
    Class<?> handlerType = (handler instanceof String ?
            obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {
        //將handlerType轉換爲用戶類型(通常等同於被轉換的類型,不過諸如CGLIB生成的子類會被轉換爲原始類型)
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        //尋找目標類型userType中的Methods,selectMethods方法的第二個參數是lambda表達式,即感興趣的方法的過濾規則
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                //回調函數metadatalookup將通過controller定義的mapping與手動定義的mapping合併起來
                (MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
        if (logger.isTraceEnabled()) {
            logger.trace("Mapped " + methods.size() + " handler method(s) for " + userType + ": " + methods);
        }
        methods.forEach((key, mapping) -> {
            //再次覈查方法與類型是否匹配
            Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
            //如果是滿足要求的方法,則註冊到全局的MappingRegistry實例裏
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

首先將參數handler(即外部傳入的BeanName或者BeanType)轉換爲Class<?>類型變量handlerType。如果轉換成功,再將handlerType轉換爲用戶類型(通常等同於被轉換的類型,不過諸如CGLIB生成的子類會被轉換爲原始類型)。接下來獲取該用戶類型裏所有的方法(Method)。循環處理每個方法,如果是滿足要求的方法,則註冊到全局的MappingRegistry實例裏。

4-4、解析Mapping信息

其中,以下代碼片段有必要深入探究一下

 Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));

MethodIntrospector.selectMethods方法的調用,將會把用@RequestMapping標記的方法篩選出來,並交給第二個參數所定義的MetadataLookup回調函數將通過controller定義的mapping與手動定義的mapping合併起來。
第二個參數是用lambda表達式傳入的,表達式中將method、userType傳給getMappingForMethod(method, userType)方法。

getMappingForMethod方法在高層次中是抽象方法,具體的是現在第4層RequestMappingHandlerMapping類中實現。在具體實現getMappingForMethod時,會調用到RequestMappingHandlerMapping類的下面這個方法。從該方法中,我們可以看到,首先會獲得參數element(即用戶在Controller中定義的方法)的RequestMapping類型的類實例,然後構造代表Mapping抽象模型的RequestmappingInfo類型實例並返回。

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

構造代表Mapping抽象模型的RequestmappingInfo類型實例,用的是createRequestMappingInfo方法,如下。可以看到RequestMappingInfo所需要的信息,包括paths、methods、params、headers、consumers、produces、mappingName,即用戶定義@RequestMapping註解時所設定的可能的參數,都被存在這兒了。擁有了這些信息,當請求來到時,RequestMappingInfo就可以測試自身是否是處理該請求的人選之一了。

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

        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name());
        if (customCondition != null) {
            builder.customCondition(customCondition);
        }
        return builder.options(this.config).build();
    }

4-5、緩存Mapping與HandlerMethod關係

最後,registerHandlerMethod(handler, invocableMethod, mapping)調用將緩存HandlerMethod,其中mapping參數是RequestMappingInfo類型的。。
內部調用的是MappingRegistry實例的void register(T mapping, Object handler, Method method)方法,其中T是RequestMappingInfo類型。
MappingRegistry類維護所有指向Handler Methods的映射,並暴露方法用於查找映射,同時提供併發控制。

public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
            try {
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                ......
                this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
            }
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }

五、匹配請求來獲得HandlerMethod

AbstractHandlerMethodMapping類的“Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange)”方法,具體實現了根據請求查找HandlerMethod的邏輯。

    @Override
    public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
        //獲取讀鎖
        this.mappingRegistry.acquireReadLock();
        try {
            HandlerMethod handlerMethod;
            try {
                //調用其它方法繼續查找HandlerMethod
                handlerMethod = lookupHandlerMethod(exchange);
            }
            catch (Exception ex) {
                return Mono.error(ex);
            }
            if (handlerMethod != null) {
                handlerMethod = handlerMethod.createWithResolvedBean();
            }
            return Mono.justOrEmpty(handlerMethod);
        }
        //釋放讀鎖
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

handlerMethod = lookupHandlerMethod(exchange)調用,繼續查找HandlerMethod。我們繼續看一下HandlerMethod lookupHandlerMethod(ServerWebExchange exchange)方法的定義。爲方便閱讀,我把註釋也寫在了代碼裏。

    protected HandlerMethod lookupHandlerMethod(ServerWebExchange exchange) throws Exception {
        List<Match> matches = new ArrayList<>();
        //查找所有滿足請求的Mapping,並放入列表mathes
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange);

        if (!matches.isEmpty()) {
            //獲取比較器comparator
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(exchange));
            //使用比較器將列表matches排序
            matches.sort(comparator);
            //將排在第1位的作爲最佳匹配項
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                //將排在第2位的作爲次佳匹配項
                Match secondBestMatch = matches.get(1);
            }
            handleMatch(bestMatch.mapping, bestMatch.handlerMethod, exchange);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), exchange);
        }
    }

六、總結

理解了Spring WebFlux在獲取映射關係方面的抽象設計模型後,就很容易讀懂代碼,進而更加理解框架的具體處理方式,在使用框架時做到“知己知彼”。

原文:http://www.yesdata.net/2018/11/27/spring-flux-request-mapping/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章