DispatcherHandler的工作原理(传统方式)

咱前面也看到了,也做过示例,咱知道 WebFlux 可以完美兼容 WebMvc 的开发风格,那自然咱就猜测,WebFlux 中的核心前端控制器 DispatcherHandler 估计能跟 DispatcherServlet 在这部分的处理上差不多,抱着这个猜测,咱来以Debug的方式来观察处理逻辑走向。

1. DispatcherHandler#handle

public Mono<Void> handle(ServerWebExchange exchange) {
    if (this.handlerMappings == null) {
        return createNotFoundError();
    }
    return Flux.fromIterable(this.handlerMappings)
        .concatMap(mapping -> mapping.getHandler(exchange))
        .next()
        .switchIfEmpty(createNotFoundError())
        .flatMap(handler -> invokeHandler(exchange, handler))
        .flatMap(result -> handleResult(exchange, result));
    }
}

这里很惊讶的发现它的源码非常少,相比较 DispatcherServlet 的代码数量,少的不是一个数量级!

注意它传入的参数是一个 ServerWebExchange ,联想 DispatcherServlet 中传入的是 HttpServletRequestHttpServletResponse ,猜想它应该是类似于这两个类的合体:

public interface ServerWebExchange {
    /**
    * Return the current HTTP request.
    */
    ServerHttpRequest getRequest();

    /**
    * Return the current HTTP response.
    */
    ServerHttpResponse getResponse();

果然从这个接口的方法中发现了 RequestResponse 的概念,证明猜想正确。

回到 handle 方法,通过Debug进来,首先要判断现有的 HandlerMapping ,当前测试的Demo工程中我分别写了基于注解的映射,和基于函数式端点的映射。Debug下发现有3个 HandlerMapping

img

之后的return结构中是一连串链式调用,这也体现出了函数式编程和响应式编程的一个弊端:Debug真的困难。我在最开始Debug找线索时,真是费了不少功夫,才慢慢找到这些端倪。

这段调用中可读性倒是挺高,咱参考之前 DispatcherServlet 的工作原理,大概分析一下这里面的三个步骤:

2. 选定HandlerMapping

不难看出,选定 HandlerMapping 的步骤是这几步:

return Flux.fromIterable(this.handlerMappings)
    .concatMap(mapping -> mapping.getHandler(exchange))
    .next()

首先进入 formInterable 方法:

2.1 Flux#fromIterable

public static <T> Flux<T> fromIterable(Iterable<? extends T> it) {
    return onAssembly(new FluxIterable<>(it));
}

它将这一组 HandlerMapping 转换成了一组 FluxIterable ,之后传入了 onAssembly 方法中。

2.2 onAssembly

protected static <T> Flux<T> onAssembly(Flux<T> source) {
    Function<Publisher, Publisher> hook = Hooks.onEachOperatorHook;
    if(hook != null) {
        source = (Flux<T>) hook.apply(source);
    }
    if (Hooks.GLOBAL_TRACE) {
        AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get());
        source = (Flux<T>) Hooks.addAssemblyInfo(source, stacktrace);
    }
    return source;
}

方法的名称可以理解为 “触发了装载、扩展动作”,这个方法的文档注释:

To be used by custom operators: invokes assembly Hooks pointcut given a Flux, potentially returning a new Flux. This is for example useful to activate cross-cutting concerns at assembly time, eg. a generalized checkpoint().

由自定义运算符使用:给定Flux调用程序集Hooks切入点,并有可能返回新的Flux。这对于在装配时激活横切关注点很有用,比方说泛型checkpoint()。

文档注释中的几个关键词已经足以给我们提供思路了:切入点、装配、横切。是不是想到了AOP?其实这个方法的作用有点类似AOP,不过我个人感觉它应该更符合装饰者模式。通过Debug,发现它只是包装为一个 FluxIterable 对象,不过这就是装载、扩展的体现:将可迭代的集合装载为一个 Flux 流。

img

2.3 concatMap(mapping -> mapping.getHandler(exchange))

上面将那一组 HandlerMapping 转换为 Flux后,接下来要从这组 HandlerMapping 中找出能处理当前请求的 HandlerMapping 了:

来到 AbstractHandlerMapping#getHandler

public Mono<Object> getHandler(ServerWebExchange exchange) {
    return getHandlerInternal(exchange).map(handler -> {
    // ......
});
}

这个方法分为两部分:先执行 getHandlerInternal 获取可以处理当前请求的 HandlerMapping ,再执行 map 方法进行后处理。

2.3.1 getHanderInternal

对于传统的 @RequestMapping 方式标注的 “Handler”,会进入 AbstractHandlerMethodMapping#getHandlerInternal 方法(又跟 WebMvc 非常相似):

public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
    this.mappingRegistry.acquireReadLock();
    try {
        HandlerMethod handlerMethod;
        try {
            // 搜索处理器方法(真正处理请求的RequestMapping)
            handlerMethod = lookupHandlerMethod(exchange);
        }
        catch (Exception ex) {
            return Mono.error(ex);
        }
        // 将方法分离出来,单独形成一个Bean
        if (handlerMethod != null) {
            handlerMethod = handlerMethod.createWithResolvedBean();
        }
        return Mono.justOrEmpty(handlerMethod);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}

对比第26篇 (DispatcherServlet 的工作原理),第5.2.1章节,发现 DispatcherServlet 中的方法更短,但相比较而言 WebFlux 中处理的更严谨(有异常处理的考虑),而WebMvc 中只是简单地把异常抛出去了而已。如果不看这些细枝末节,会发现思路完全一致:先搜索所有 “HandlerMethod”,后封装为一个单独的 HandlerMethod 类型的Bean。

方法内部的实现与 WebMvc 部分思路几乎完全一致,不再深扒,这里咱瞄一眼扫描到的匹配请求的 HandlerMethod

img

它已经找到了我写的 DemoController

2.3.2 搜索到后的map

return getHandlerInternal(exchange).map(handler -> {
    if (logger.isDebugEnabled()) {
        logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
    }
    if (CorsUtils.isCorsRequest(exchange.getRequest())) {
        CorsConfiguration configA = this.corsConfigurationSource.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;
});

可以看出来它这里面有对跨域的处理,由于咱这是最简单的测试,自然不会触发跨域处理,直接把封装好的 Mono<HandlerMethod> 返回回去了。


返回回去后,回到 DispatcherHandlerhandle 方法,要寻找这个 HandlerMethod 对应的 HandlerAdapter 了:

3. 寻找HandlerAdapter

private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
            if (handlerAdapter.supports(handler)) {
                return handlerAdapter.handle(exchange, handler);
            }
        }
    }
    return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}

由于是 WebMvc 开发风格的处理,返回的 HandlerMethod 也是 DemoController 里的,默认情况下有3个 HandlerAdapter :

img

不用想,肯定走 RequestMappingHandlerAdapter ,通过Debug发现确实如此:

img

之后直接调了它的 handle 方法了!

4. 【目标方法】HandlerAdapter#handle

来到 RequestMappingHandlerAdapter 中:

public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    Assert.state(this.methodResolver != null && this.modelInitializer != null, "Not initialized");

    // 4.1 初始化参数绑定上下文
    InitBinderBindingContext bindingContext = new InitBinderBindingContext(
        getWebBindingInitializer(), this.methodResolver.getInitBinderMethods(handlerMethod));

    // 4.2 创建方法执行对象
    InvocableHandlerMethod invocableMethod = this.methodResolver.getRequestMappingMethod(handlerMethod);

    // 异常处理器的准备
    Function<Throwable, Mono<HandlerResult>> exceptionHandler =
        ex -> handleException(ex, handlerMethod, bindingContext, exchange);

    // 4.3 执行目标方法,处理返回值和异常
    return this.modelInitializer
        .initModel(handlerMethod, bindingContext, exchange)
        .then(Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext)))
        .doOnNext(result -> result.setExceptionHandler(exceptionHandler))
        .doOnNext(result -> bindingContext.saveModel())
        .onErrorResume(exceptionHandler);
}

这里面先是对 HandlerMethod 进行一些处理,以及异常处理器的处理,最后的链式调用中第三行有一个 invocableMethod.invoke ,它就会去引导调用真正的 Controller 方法。根据上面源码的注释标注咱一样一样来看:

4.1 InitBinderBindingContext

这个类非常有意思,它在 SpringFramework 的官方API中根本没有收录进去,而且也找不到任何相关的资料,只能靠源码的部分来大概看一眼它:

/** Extends BindingContext with @InitBinder method initialization. */
class InitBinderBindingContext extends BindingContext

它的文档注释中提到了 @InitBinder 注解,这个注解咱之前在 WebMvc 部分见过,它是做参数绑定的。另外它继承自 BindingContext ,而 BindingContext 咱看类名能猜测可能是跟 @InitBinder 注解配合做参数绑定相关工作的。看一眼它的文档注释:

Context to assist with binding request data onto Objects and provide access to a shared Model with controller-specific attributes. Provides methods to create a WebExchangeDataBinder for a specific target, command Object to apply data binding and validation to, or without a target Object for simple type conversion from request values. Container for the default model for the request.

用于帮助将请求数据绑定到对象的上下文,并提供对具有控制器特定属性的共享模型的访问。

提供用于为特定目标创建 WebExchangeDataBinder 的方法,提供对目标对象应用数据绑定和验证的命令Object(或不具有目标对象的命令Object)以从请求值进行简单类型转换的方法。

请求的默认模型的容器。

文档注释倒是可以比较明确的看出,它确实是跟参数绑定相关的组件。

4.2 InvocableHandlerMethod

这个组件咱之前在 WebMvc 部分也见过,它的作用就是调用 HandlerMapping 中真正封装的 Controller 方法。注意到它跟 WebMvc 部分的 ServletInvocableHandlerMethod 应该是继承和被继承关系,只不过在 WebFlux 中见到的 InvocableHandlerMethod 是在 reactive 包下的,而不是 mvc 包。其余部分几乎完全相同,小伙伴们可以将之前第26篇的 DispatcherServlet 工作原理进行对照翻看,小册不再赘述。

4.3 执行目标方法,处理返回值和异常

return this.modelInitializer
    .initModel(handlerMethod, bindingContext, exchange)
    .then(Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext)))
    .doOnNext(result -> result.setExceptionHandler(exceptionHandler))
    .doOnNext(result -> bindingContext.saveModel())
    .onErrorResume(exceptionHandler);

注意这里面最重要的动作就是 invocableMethod.invoke(exchange, bindingContext) ,来看它的实现,进入到 InvocableHandlerMethod 中:

public Mono<HandlerResult> invoke(
    ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) {
    return getMethodArgumentValues(exchange, bindingContext, providedArgs).flatMap(args -> {
        Object value;
        try {
            //4.3.1 执行目标方法
            ReflectionUtils.makeAccessible(getBridgedMethod());
            value = getBridgedMethod().invoke(getBean(), args);
        }
        // catch ......

        // ......
    });
}

它直接就 return 了,里面又是流式调用,lambda表达式中有一部分吸引了我:try块中的 value = getBridgedMethod().invoke(getBean(), args); ,这个套路似曾相识啊,咱把 WebMvc 中的调用方式一块拿出来对比一下:

4.3.1 WebMvc 和 WebFlux 部分的Controller方法反射调用

// org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod (注意看包名)
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
                            Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);

    ------ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ------

    // org.springframework.web.method.support.InvocableHandlerMethod (注意看包名)
    protected Object doInvoke(Object... args) throws Exception {
        ReflectionUtils.makeAccessible(getBridgedMethod());
        try {
            return getBridgedMethod().invoke(getBean(), args);
        }
        // catch ......
    }

竟然是一模一样的!是不是产生了一种感觉:WebFlux 不会基本上都是抄的 WebMvc 吧。。。(然而事实还真是)

执行完目标方法后,回到 invoke 方法,要进行返回值处理:

try {
    ReflectionUtils.makeAccessible(getBridgedMethod());
    value = getBridgedMethod().invoke(getBean(), args);
} // catch ......

// 处理响应状态
HttpStatus status = getResponseStatus();
if (status != null) {
    exchange.getResponse().setStatusCode(status);
}

MethodParameter returnType = getReturnType();
// 4.3.2 处理返回值
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(returnType.getParameterType());
boolean asyncVoid = isAsyncVoidReturnType(returnType, adapter);
if ((value == null || asyncVoid) && isResponseHandled(args, exchange)) {
    return (asyncVoid ? Mono.from(adapter.toPublisher(value)) : Mono.empty());
}

HandlerResult result = new HandlerResult(this, value, returnType, bindingContext);
return Mono.just(result);

上面处理响应状态后,下面要处理返回值了:

4.3.2 处理返回值

由于之前咱在 DemoController 中声明的返回值类型是 Mono<String> ,那这里的 value 也就是 Mono<String> 类型。通过Debug,发现它的类型和对应的 ReactiveAdapter 也都获取到了:

img

多提一嘴,ReactiveAdapter 就是通过上一篇咱看自动装配中看到的 ReactiveAdapterRegistry 组件获取而来,用它来实际兼容 RxJava 等其他响应式编程框架。

最后,它会将返回值封装为一个 HandlerResult 对象,返回出去。

5. 最后的返回值处理

回到 DispatcherHandler 中:

return Flux.fromIterable(this.handlerMappings)
    .concatMap(mapping -> mapping.getHandler(exchange))
    .next()
    .switchIfEmpty(createNotFoundError())
    .flatMap(handler -> invokeHandler(exchange, handler))
    .flatMap(result -> handleResult(exchange, result));

上一步执行完成的是 handler -> invokeHandler ,下面还要再处理上一步的返回值:

private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
    return getResultHandler(result).handleResult(exchange, result)
        .onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exceptionResult ->
                                                                      getResultHandler(exceptionResult).handleResult(exchange, exceptionResult)));
}

又是一个调用链,它先根据上一步的返回值,获取到 ResultHandler ,之后处理一下,返回。

5.1 getResultHandler

private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {
    if (this.resultHandlers != null) {
        for (HandlerResultHandler resultHandler : this.resultHandlers) {
            if (resultHandler.supports(handlerResult)) {
                return resultHandler;
            }
        }
    }
    throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());
}

它的搜索方法还是循环所有的 HandlerResultHandler ,而默认情况下一共有4个 HandlerResultHandler

img

很容易判断,通过 @RestController 出来的方法,都会走 ResponseBodyResultHandler

img

5.2 ResponseBodyResultHandler#handleResult

public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
    Object body = result.getReturnValue();
    MethodParameter bodyTypeParameter = result.getReturnTypeSource();
    return writeBody(body, bodyTypeParameter, exchange);
}

在这里它会将返回值拿到,之后执行 writeBody 方法将返回值结果写到响应流中,处理结束。

handleResult 方法执行完毕后,回到 DispatcherHandler 中,链式调用的链也执行完毕了,整个请求处理结束。

6. 流程图

img

7. 小结

  1. DispatcherHandlerDispatcherServlet 的处理思路几乎完全相同,包括寻找 HandlerMappingHandlerAdapter 、执行目标方法等。

  2. DispatcherHandler 相比较 DispatcherServlet 最大的不同点,是里面的实现绝大部分都采用响应式流编程。

最后更新于