RequestMappingHandlerAdapter 源码解析

RequestMappingHandlerAdapter 源码解析

我们在《SpringMVC- 第三篇:在控制器方法中获取请求参数和构建响应.md》中可以知道,在控制器方法中,通过在参数前添加特定的注解或者使用特定的参数名称,SpringMVC 会自动地注入这些参数的具体值,这是怎么实现的?

实际上我们在 DispatcherServlet#doDispatch 中首先通过 HandlerMapping 进行处理器映射,获取 handler(实际上获取的是包含拦截器的处理器执行链 HandlerExecutionChain),获取 handler 之后,并不是直接执行,而是通过 HandlerExecutionChain 中最终的 handler,找到这个 handler 的适配器 HandlerAdapter,然后执行 HandlerAdapter#handle 方法执行处理器,具体代码在 DispatcherServlet#doDispatch 方法中:

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
......
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

HandlerAdapter#handle 方法最终会执行 handler 方法,但是在此之前,一般会进行一些准备工作。当 handler 的类型是 HandlerMethod 即控制器方法的时候,适配器是 RequestMappingHandlerAdapter,我们会在控制器方法执行前,进行参数的解析填充,还会进行一下其他的工作。

关于适配器的意义和完整的分类请看《SpringMVC 控制器方法(handler)适配器 - HandlerAdapter.md

接下来,我们就开始分析 RequestMappingHandlerAdapter 的源码

简单的源码分析

我们有必要简单了解一下 HandlerMethod,我们之前在《SpringMVC-RequestMappingHandlerMapping 源码解析.md》的 初始化 小节中简单了解过 HandlerMethod,现在,我们进行详细的解读。

HandlerMethod

`HandlerMethod`` 是一个类,作用是封装由方法和 bean 构成的 handler 的信息。提供对方法参数、方法返回值、方法注解等信息的统一的方便访问。

HandlerMethod 类实例可以使用 bean 实例或 bean 名称创建 (例如 lazy-init bean, prototype bean)。

继承树

InvocableHandlerMethod - 重点 - 参数解析器生效

InvocableHandlerMethod 继承了 HandlerMethod,作用是通过引入 HandlerMethodArgumentResolverComposite(参数解析器)、ParameterNameDiscoverer(参数名称发现器)、WebDataBinderFactory(数据绑定器),从当前 HTTP 请求中解析出控制器方法的参数,然后带着这些参数,调用控制器方法,简而言之,其拓展的功能就是参数解析

核心方法是 InvocableHandlerMethod#invokeForRequest

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 从request请求中获取方法所需的参数,其中 providedArgs 为提供的默认值,根据类型匹配,如果参数在 providedArgs 存在,则不从request中解析
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // 带着解析出的参数,调用handler对应的方法,即控制器方法
    return doInvoke(args);
}

其中先是调用了 getMethodArgumentValues 解析方法参数

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 获取handler的所有参数 
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }

    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        // 初始化参数名称探索器
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        // 遍历 providedArgs 根据类型与参数匹配,
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            // 如果匹配上,则直接返回,不再从equest中解析
            continue;
        }
        // 当前所有参数解析器是否都不支持此参数的类型,直接报错
        // resolvers 的类型为 HandlerMethodArgumentResolverComposite,表示多个HandlerMethodArgumentResolve的聚合,同时在进行查找的时候,还添加了缓存,Spring中真的是随处可见的缓存
        // 这是一种常见的设计模式,
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            // 如果支持,则进行解析
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            // Leave stack trace for later, exception may actually be resolved and handled...
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}

注意,resolvers 的类型为 HandlerMethodArgumentResolverComposite,也实现了 HandlerMethodArgumentResolver 接口,其作用是将多个 HandlerMethodArgumentResolve 的列表封装起来像一个 HandlerMethodArgumentResolve 一样对外提供参数解析服务。这其中涉及到循环遍历,不过 HandlerMethodArgumentResolverComposite 也添加了缓存,提升了查询速度(Spring 中真的是随处可见的缓存)。

核心方法是 HandlerMethodArgumentResolverComposite#getArgumentResolver参数解析器的顺序很重要,如果两个参数解析器都能解析一个参数,那么排在前面的就会被采用,排在后面的就不会生效

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 现在缓存中查找,没有找到才开始遍历
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        // 开始遍历
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                // 如果某一个参数解析器支持此参数,则将其添加到缓存中,同时跳出循环,返回结果
                // 说明参数解析器的顺序很重要,如果两个参数解析器都能解析一个参数,那么排在前面的就会被采用,排在后面的就不会生效
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

HandlerMethodArgumentResolverCompositesupportsParameterresolveArgument 的实现都是调用的 getArgumentResolver 方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
    return getArgumentResolver(parameter) != null;
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

getMethodArgumentValues 解析方法参数之后,最终带着解析出来的参数,调用控制器方法。学习了后面的 RequestMappingHandlerAdapter#invokeHandlerMethod 之后,我们就会发现,RequestMappingHandlerAdapter 适配器最终调用控制器方法,就是在这里调用的

protected Object doInvoke(Object... args) throws Exception {
    // 返回实际的handler,即控制器方法
    Method method = getBridgedMethod();
    // 通过反射解析权限问题
    ReflectionUtils.makeAccessible(method);
    try {
        if (KotlinDetector.isSuspendingFunction(method)) {
            return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
        }
        // 通过反射直接调用
        return method.invoke(getBean(), args);
    }
    catch (IllegalArgumentException ex) {
        assertTargetBean(method, getBean(), args);
        String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
        throw new IllegalStateException(formatInvokeError(text, args), ex);
    }
    catch (InvocationTargetException ex) {
        // Unwrap for HandlerExceptionResolvers ...
        ......
        // 各种报错处理
    }
}
ParameterNameDiscoverer

ParameterNameDiscoverer 的作用是用于发现方法和构造函数的参数名,只有两个方法待实现

有各种方法(实现)来发现参数名称,其中默认实现是 DefaultParameterNameDiscovererDefaultParameterNameDiscoverer 继承自 PrioritizedParameterNameDiscovererPrioritizedParameterNameDiscoverer 相当是多个 ParameterNameDiscoverer 实现的实例聚合而成的 ParameterNameDiscoverer 实例,每次查询参数名都会遍历这些实例,且根据 ParameterNameDiscoverer 实现的实例的添加顺序进行查询,查到了参数名就返回。

默认会添加以下两种 ParameterNameDiscoverer 实现

一般都是 LocalVariableTableParameterNameDiscoverer 返回参数名,它使用方法属性中的 LocalVariableTable 信息来发现参数名。如果类文件在编译时没有调试信息,则返回 null。

StandardReflectionParameterNameDiscoverer 很有意思,它使用 JDK 8 的反射功能来反射参数名 (基于 -parameters 编译器参数)。例如我们可以在 POM 文件中添加参数

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <compilerArgs>
                    <arg>-parameters</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>
ServletInvocableHandlerMethod - 重点 - 返回值处理器生效

ServletInvocableHandlerMethod 继承了 InvocableHandlerMethod,通过引入 HandlerMethodReturnValueHandlerComposite(返回值解析器),使其能够通过注册的 HandlerMethodReturnValueHandler 处理方法返回值,还支持基于方法级别的 @ResponseStatus 注解设置响应状态。

核心方法是:ServletInvocableHandlerMethod#invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 调用父类`InvocableHandlerMethod#invokeForRequest`方法进行方法参数解析,然后调用,获取返回值 
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 设置响应状态
    setResponseStatus(webRequest);

    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            // 设置mavContainer的 requestHandled属性为true,表示请求已经被完全处理完,不需要视图解析。
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        // 看看控制器方法在处理请求的时候是不是报了什么错
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 调用返回值处理器进行返回值处理
        this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

注意,returnValueHandlers 的类型为 HandlerMethodReturnValueHandlerCompositeHandlerMethodReturnValueHandlerComposite 也实现了 HandlerMethodReturnValueHandler,其作用是将多个 HandlerMethodReturnValueHandler 的列表封装起来像一个 HandlerMethodReturnValueHandler 一样对外提供参数解析服务。这其中涉及到循环遍历,但是HandlerMethodArgumentResolverComposite 不同,没有缓存。这里很明显是有优化的空间的,我们直接看 handleReturnValue 方法:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    // 获取能处理该返回值的返回值处理器
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    // 调用该返回值处理器
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    // 是否是异步返回值
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        // 如果是异步返回值,则只能返回 AsyncHandlerMethodReturnValueHandler
        // 如果不是异步返回值,则直接判断 handler.supportsReturnType
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

private boolean isAsyncReturnValue(@Nullable Object value, MethodParameter returnType) {
    // 如何判断是不是异步返回值,
    // 遍历一遍,通过 AsyncHandlerMethodReturnValueHandler 的 isAsyncReturnValue 来判断
    // 这里肯定是有优化的空间的,很明显可以加缓存
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (handler instanceof AsyncHandlerMethodReturnValueHandler && ((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) {
            return true;
        }
    }
    return false;
}

AbstractHandlerMethodAdapter

AbstractHandlerMethodAdapter 是支持 HandlerMethod 类型 handler(也就是控制器方法类型的 handler)的 HandlerAdapter 接口的父抽象类。

内容也很简单,实现 HandlerAdapter 接口的 handle 方法,并将其委托给 AbstractHandlerMethodAdapter#handleInternal,这个方法实际是个空方法,意思是让子类来实现。实际上 AbstractHandlerMethodAdapter 也只有一个子类 RequestMappingHandlerAdapter

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    // 因为已经很明确handler的类型是 HandlerMethod 所以这里直接强转
    return handleInternal(request, response, (HandlerMethod) handler);
}

RequestMappingHandlerAdapter

继承 AbstractHandlerMethodAdapter,支持 @RequestMapping 注解修饰的 handler,即 HandlerMethods 类型的 handler。

对自定义参数和返回值类型的支持可以通过 setCustomArgumentResolverssetCustomReturnValueHandlers 添加,或者,使用 setArgumentResolverssetReturnValueHandlers 重新配置所有参数和返回值类型

RequestMappingHandlerAdapter 实现了 InitializingBean 接口,那我们就直接看 RequestMappingHandlerAdapter#afterPropertiesSet 方法

public void afterPropertiesSet() {
    // 检查是否有全局的 @ModelAttribute方法和@InitBinder方法,并进行缓存,
    // 检查有没有实现了 RequestBodyAdvice 和 ResponseBodyAdvice 的 ControllerAdviceBean 类,并进行缓存
    initControllerAdviceCache();

    if (this.argumentResolvers == null) {
        // 初始化参数解析器
        // 如果没有初始化 argumentResolvers,则 调用 getDefaultArgumentResolvers添加默认的参数解析器,一般有26个
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.initBinderArgumentResolvers == null) {
        // 初始化专用于 @InitBinder方法 的参数解析器
        // 如果没有初始化 initBinderArgumentResolvers 调用 getDefaultInitBinderArgumentResolvers 添加默认的参数解析器,一般有13个
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        // 初始化返回值处理器
        // 如果没有初始化 returnValueHandlers 调用 getDefaultReturnValueHandlers 添加默认的返回值处理器,一般有15个
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

从这个方法中初始化的内容,我们大概可以推断出整个 RequestMappingHandlerAdapter 的工作包含三个部分,我们对 RequestMappingHandlerAdapter 的源码分析也将主要分这几个方面。

接下来看对 HandlerAdapter#handle 方法的实现,即 RequestMappingHandlerAdapter#handleInternal,这个类主要是对请求进行一下检查,然后看是否有必要将在同一 session 中对所有的对控制器方法的调用串行化,最终将对控制器方法的调用委托给 RequestMappingHandlerAdapter#invokeHandlerMethod,这才是我们的主菜。

protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    // 检查请求是否是指定的几个请求方法,是否包含session
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            // 如果 synchronizeOnSession 为true,且 session 不为空,那么同一个session中,对控制器方法的调用将是单线程的,
            // 也就是说调用第一个控制器方法没有处理完的时候,再次发起请求,需要控制器方法处理,那就只能等着,只能等第一个处理完,才能处理第二个
            // 概括一下就是,设置控制器执行是否应在同一session上串行,而不是并发,将来自同一客户端的并行调用串行化
            synchronized (mutex) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No HttpSession available -> no mutex necessary
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // No synchronization on session demanded at all...
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }

    return mav;
}

继续看 RequestMappingHandlerAdapter#invokeHandlerMethod,这才是我们的主菜。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 根据外部@ControllerAdvice中的@InitBinder方法(排在前)和控制器方法所在的控制器内部的@InitBinder方法(排在后)初始化 WebDataBinderFactory
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // 根据外部@ControllerAdvice中的@ModelAttribute方法(排在前)和控制器方法所在的控制器内部的@ModelAttribute方法(排在后)初始化 ModelFactory
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        // 将 handlerMethod 封装为 ServletInvocableHandlerMethod,
        // 实际上 ServletInvocableHandlerMethod 是 handlerMethod 的子类
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            // 设置参数解析器
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            // 设置返回值解析器
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        // 设置值绑定工厂
        invocableMethod.setDataBinderFactory(binderFactory);
        // 设置参数名发现器
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        // 初始化 Model,依次调用前面在初始化 ModelFactory 时传入的 @ModelAttribute方法,
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // 异步请求的相关处理   
        ......

        // 执行控制器方法,在执行控制器方法前,如果需要的话,会依次调用WebDataBinderFactory中的 @InitBinder方法 
        invocableMethod.invokeAndHandle(webRequest, mavContainer);

        // 异步请求的相关处理   
        ......

        // 从 经过返回值处理器处理过的 mavContainer 中提取出 ModelAndView 对象来进行后续处理
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

最终的 invocableMethod.invokeAndHandle(webRequest, mavContainer); 实际上就是我们前面在分析 ServletInvocableHandlerMethod 的时候,分析过的 ServletInvocableHandlerMethod#invokeAndHandle

我们来简单看看 getModelAndView 方法

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    modelFactory.updateModel(webRequest, mavContainer);
    // 如果我们前面在返回值解析器中设置过 requestHandled 这个标志位为 true,(这个标志为的意思是不需要进行后续的视图解析),
    // 那么此时就会直接返回 null,表示不需要后续的视图解析了。具体表现就是在 DispatcherServlet的processDispatchResult会直接不进行处理
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    ModelMap model = mavContainer.getModel();
    // 构建 ModelAndView 对象
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}

注意:RequestMappingHandlerAdapter 是所有控制器方法类型(HandlerMethod)的 handler 的 adapter,所有的 @Controller 下的 @RequestMapping 方法的执行都会通过此 adapter,所以 RequestMappingHandlerAdapter 是全局的,如果你想对所有的控制器方法的执行进行设置,或者进行别的自定义操作,那就应该想办法在 RequestMappingHandlerAdapter 中进行,比如参数解析器和返回值解析器,比如在同一 session 中对所有的对控制器方法的调用串行化

跨控制器共享相关处理

@ControllerAdvice 修饰的类中的 @ModelAttribute 方法和 @InitBinder 方法的缓存和生效过程,在《SpringMVC- 第八篇:跨控制器间共享.md》的 @ModelAttribute方法和@InitBinder方法 小节中已经说的非常清楚了。这里就不多赘述了。

参数解析器

参数解析器的作用是,确定 HandlerMethod 对应的方法的每一个参数的值是什么,一般 HandlerMethod 都对应着一个控制器方法,因此参数解析器的作用就是确定控制器方法的参数的值,我们在编写控制器方法的时候,能使用哪些类型的参数,实际上就取决于当前容器中有哪些参数解析器

Spring 的控制器方法默认支持哪些参数类型,看官方文档:Spring MVC Method Arguments

初始化

我们有两种初始化 RequestMappingHandlerAdapter 中的参数解析器的方式,

在前面对 RequestMappingHandlerAdapterafterPropertiesSet 方法的解析可知,如果 argumentResolvers 为 null(说明未调用 RequestMappingHandlerAdapter#setArgumentResolvers 设置参数解析器),则调用 RequestMappingHandlerAdapter#getDefaultArgumentResolvers 对其进行初始化,其中包含了 RequestMappingHandlerAdapter#setCustomArgumentResolvers 添加的自定义参数解析器:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

    // 注解修饰参数的参数解析器
    // @RequestParam注解
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    // @PathVariable注解
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    // @MatrixVariable注解
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    // @ModelAttribute注解
    resolvers.add(new ServletModelAttributeMethodProcessor(false));
    // @RequestBody注解
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    // @RequestPart注解
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
    // @RequestHeader注解
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new RequestHeaderMapMethodArgumentResolver());
    // @CookieValue注解
    resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
    // @Value注解 ,也就是说,我们在控制器方法参数中可以直接使用 @Value 注解来获取信息
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    // @SessionAttribute注解
    resolvers.add(new SessionAttributeMethodArgumentResolver());
    // @RequestAttribute注解
    resolvers.add(new RequestAttributeMethodArgumentResolver());

    // 特定类型的参数的参数解析器
    // WebRequest ServletRequest MultipartRequest HttpSession InputStream Reader HttpMethod Locale 等等
    resolvers.add(new ServletRequestMethodArgumentResolver());
    // ServletResponse OutputStream Writer
    resolvers.add(new ServletResponseMethodArgumentResolver());
    // HttpEntity RequestEntity
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    // RedirectAttributes
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    // Model
    resolvers.add(new ModelMethodProcessor());
    // Map
    resolvers.add(new MapMethodProcessor());
    // Errors
    resolvers.add(new ErrorsMethodArgumentResolver());
    // SessionStatus
    resolvers.add(new SessionStatusMethodArgumentResolver());
    // UriComponentsBuilder ServletUriComponentsBuilder
    resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
    if (KotlinDetector.isKotlinPresent()) {
        resolvers.add(new ContinuationHandlerMethodArgumentResolver());
    }

    // 自定义的参数的参数解析器
    // 通过 RequestMappingHandlerAdapter 的 setCustomArgumentResolvers 方法添加 
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    // 用于兜底的
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
    resolvers.add(new ServletModelAttributeMethodProcessor(true));

    return resolvers;
}

抛开自定义的参数解析器和 Kotlin 场景下的解析器,Spring 内置的参数解析器有 26 个。

生效

RequestMappingHandlerAdapter#invokeHandlerMethod 可知,将其设置到 ServletInvocableHandlerMethod 实例中的 resolvers 属性(继承自 InvocableHandlerMethod)中,在调用 ServletInvocableHandlerMethod#invokeAndHandle 的时候,先调用 invokeForRequest 方法(继承自 InvocableHandlerMethod)进行请求的处理,此时就会用到 resolvers 属性进行参数解析。很简单,具体细节请看此篇文章前面提到的 InvocableHandlerMethod

HandlerMethodArgumentResolver

这是一个策略接口,用于在给定请求的上下文中将方法参数解析为参数值。

有两个方法:

子类实现:

RequestMappingHandlerAdapter#getDefaultArgumentResolvers 方法添加的默认参数解析器基本都在里面。

ServletRequestMethodArgumentResolver

控制器方法对原生 Servlet API 的参数类型的支持,是通过 ServletRequestMethodArgumentResolver 来实现的,主要支持这些参数:

我们来简单看看源码:

ServletRequestMethodArgumentResolver#supportsParameter

@Override
public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
            ServletRequest.class.isAssignableFrom(paramType) ||
            MultipartRequest.class.isAssignableFrom(paramType) ||
            HttpSession.class.isAssignableFrom(paramType) ||
            (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
            (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
            InputStream.class.isAssignableFrom(paramType) ||
            Reader.class.isAssignableFrom(paramType) ||
            HttpMethod.class == paramType ||
            Locale.class == paramType ||
            TimeZone.class == paramType ||
            ZoneId.class == paramType);
}

ModelMethodProcessor

ModelMethodProcessor 的作用是解析 Model 类型的参数和处理 Model 类型的返回值。

Model 类型的返回值有一种设置的效果,因此,这个 handler 应该配置在支持任何带有 @ModelAttribute@ResponseBody 注解的返回值类型的 handler 之前,以确保它们不会接管。

对 Model 类型的参数的解析,实际上是返回 ModelAndViewContainer 中的 Model。

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
    return mavContainer.getModel();
}

MapMethodProcessor

MapMethodProcessor 的作用是解析 Map 类型的参数和处理 Map 类型的返回值。

根据 @ModelAttribute@ResponseBody 等注解的存在,可以以多种方式解释 Map 类型的返回值。在 5.2 中,如果参数被注释,这个解析器将返回 false。

对 Map 类型的参数的解析,实际上是返回 Model

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
    // 实际上是直接返回 model
    return mavContainer.getModel();
}

所以本质上,控制器方法中,Map 类型的参数跟 Model 类型的参数没有啥区别,是同一个对象

ServletModelAttributeMethodProcessor - 重点

处理自定义类型的控制器方法参数,对应《SpringMVC- 第三篇:在控制器方法中获取请求参数和构建响应.md》中的 直接用POJO(JavaBean)来接受参数 小节中提到的方式。

同时也支持 @ModelAttribute 注解修饰的控制器方法参数。对应《SpringMVC- 第四篇:域对象共享数据.md》中的 在@RequestMapping方法中的方法参数上使用 小节中的用法

这两个功能其实是同一个,本质是因为这两种情况的本质都是参数解析器 ServletModelAttributeMethodProcessor 在发挥作用。

ModelAttributeMethodProcessor

同时实现 HandlerMethodArgumentResolver 接口和 HandlerMethodReturnValueHandler 接口,作用是解析 @ModelAttribute 注解修饰的方法参数并处理 @ModelAttribute 注解修饰的方法的返回值。

Model attribute(翻译为 model 属性),要么从 ModelAndView 的 model 中获取,要么使用 attribute 的类型的默认构造函数进行创建然后添加到 model 中。attribute 的实例一旦创建了,就会通过数据绑定,用 Servlet 请求参数来填充实例的属性。如果参数使用了 @javax.validation.Valid 注解,或者 Spring 自己的 @org.springframework.validation.annotation.Validated 注解,则会进行规则验证。

关于数据验证请参考《JavaBeanValidation+HibernateValidator.md》和《SpringMVC 整合 JavaBeanValidation 及拓展.md

注意,当构造 ModelAttributeMethodProcessor 的时候传入的构造器参数annotationNotRequired为 true,任何非简单类型参数和返回值都被视为模型属性(ModelAttribute),无论是否存在 @ModelAttribute 注解。也就是说在控制器方法中使用自定义 POJO 作为参数的时候会自动将其添加到 model 域中。自定义 POJO 作为参数实际上是当作 ModelAttribute 在生效的。(对应《SpringMVC- 第三篇:在控制器方法中获取请求参数和构建响应.md》中的 直接用POJO(JavaBean)来接受参数 小节中提到的方式。)

我们直接看参数解析相关的源码

先看 supportsParameter 方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
    // 如果参数有注解@ModelAttribute修饰,则支持,
    // 或者 annotationNotRequired 为true,同时参数的类型不是简单类型
    return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}

怎么判断参数类型是不是简单类型?直接看 BeanUtils#isSimpleProperty 方法的源码

//  判断是不是简单类型,先看这个类型是不是简单类型,或者是不是简单类型的元素组成的数组
public static boolean isSimpleProperty(Class<?> type) {
    Assert.notNull(type, "'type' must not be null");
    return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}

//  是不是简单类型,看看是不是在以下集合中
// 0. 非 Void类型 或者 void类型
// 1. 原始数据类型,或者原始数据类型的包装类型,
// 2. 枚举
// 3. 字符串或其他字符相关类型
// 4. 数字
// 5. 日期
// 6. Temporal
// 7. URI 或者 URL 
// 8. Locale  
// 9. Class  
public static boolean isSimpleValueType(Class<?> type) {
    return (Void.class != type && void.class != type &&
            (ClassUtils.isPrimitiveOrWrapper(type) ||
            Enum.class.isAssignableFrom(type) ||
            CharSequence.class.isAssignableFrom(type) ||
            Number.class.isAssignableFrom(type) ||
            Date.class.isAssignableFrom(type) ||
            Temporal.class.isAssignableFrom(type) ||
            URI.class == type ||
            URL.class == type ||
            Locale.class == type ||
            Class.class == type));
}

先看这个类型是不是简单类型,或者是不是简单类型的元素组成的数组,是不是简单类型,看看是不是在以下集合中

可以看到自定义类型的参数,肯定是非简单类型。

再看看 resolveArgument 方法:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
    Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

    // 1. 首先获取参数的名字,
    // 注意,这个参数的名字不是参数在控制器方法的参数列表中的名字,而是这个参数存到model中的的时候的属性名,
    // 首先会尝试从修饰此参数的@ModelAttribute注解的配置中name属性中获取,如果此参数没有@ModelAttribute注解修饰,则会根据惯例,直接使用参数类名然后将首字母小写之后作为保存到model的属性名
    String name = ModelFactory.getNameForParameter(parameter);
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    if (ann != null) {
        // 如果此参数有@ModelAttribute注解修饰,则设置以下此model属性要不要与请求参数进行数据绑定,即根据请求参数的内容,更新此model属性实例的各个属性的内容
        // 在后面进行数据绑定的时候会使用到此时的设置
        mavContainer.setBinding(name, ann.binding());
    }

    Object attribute = null;
    BindingResult bindingResult = null;

    // 2. 检查model中是否已经存在此属性, 
    if (mavContainer.containsAttribute(name)) {
        // 如果model中已经存在了此属性,则直接使用即可
        attribute = mavContainer.getModel().get(name);
    }
    else {
        // 如果model中不存在此属性,则需要创建实例
        try {
            attribute = createAttribute(name, parameter, binderFactory, webRequest);
        }
        catch (BindException ex) {
            if (isBindExceptionRequired(parameter)) {
                // No BindingResult parameter -> fail with BindException
                throw ex;
            }
            // Otherwise, expose null/empty value and associated BindingResult
            if (parameter.getParameterType() == Optional.class) {
                attribute = Optional.empty();
            }
            else {
                attribute = ex.getTarget();
            }
            bindingResult = ex.getBindingResult();
        }
    }

    if (bindingResult == null) {
        // Bean property binding and validation;
        // skipped in case of binding failure on construction.
        // 3. 与请求参数进行数据绑定
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            // 是否需要进行数据绑定
            if (!mavContainer.isBindingDisabled(name)) {
                // 如果需要,则进行绑定,attribute 实例的内容发生变化
                bindRequestParameters(binder, webRequest);
            }
            // 4. 如果有相关注解修饰,则需要进行验证
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }
        // Value type adaptation, also covering java.util.Optional
        if (!parameter.getParameterType().isInstance(attribute)) {
            attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
        }
        bindingResult = binder.getBindingResult();
    }

    // Add resolved attribute and BindingResult at the end of the model
    // 5. bindingResultModel中的结果,就是 attribute 实例和数据绑定的结果,
    // 现在 将 attribute 实例和数据绑定的结果 都放到model中
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);

    return attribute;
}

参数解析总共分 5 步:

  1. 获取此参数放入 model 时的属性名

  2. 检查 model 中是否已经存在此属性,如果 model 中已经存在了此属性,则直接使用即可,如果 model 中不存在此属性,则需要创建实例。

  3. 与请求参数进行数据绑定

    请参考《SpringMVC 中的数据绑定.md

  4. 如果有相关注解修饰,则需要进行验证

    关于数据验证请参考《JavaBeanValidation+HibernateValidator.md》和《SpringMVC 整合 JavaBeanValidation 及拓展.md

  5. 将数据绑定好的实例放入 model 中,同时将数据绑定结果也放入 model 中。

返回值解析相关的源码很简单

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    // 如果方法有注解@ModelAttribute修饰,则支持,
    // 或者 annotationNotRequired 为true,同时返回值的类型不是简单类型
    return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
            (this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
}

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // 返回值处理器做的事情也很简单,就是将返回值放到model中而已
    if (returnValue != null) {
        // 首先会尝试从修饰此方法的@ModelAttribute注解的配置中name属性中获取,如果此方法没有@ModelAttribute注解修饰,则会根据惯例,直接使用返回类型的类名然后将首字母小写之后作为保存到model的属性名
        String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
        mavContainer.addAttribute(name, returnValue);
    }
}
ServletModelAttributeMethodProcessor

ServletModelAttributeMethodProcessor 继承了 ModelAttributeMethodProcessor。并重写了相关方法。

其定位是,特定于 servlet 的 ModelAttributeMethodProcessor,它通过 ServletRequestDataBinder 类型的 WebDataBinder 进行数据绑定。

ServletModelAttributeMethodProcessor 还添加了一个默认策略,默认从 URI 模板变量或从请求参数实例化 ModelAttribute(如果名称与模型属性名称匹配并且有适当的类型转换策略的话),但是用的不多。

ServletModelAttributeMethodProcessor 的注册

RequestMappingHandlerAdapter#getDefaultArgumentResolvers 中注册了两个 ServletModelAttributeMethodProcessor,其中构造函数的参数annotationNotRequired不一样,一个是 false,排在前面,一个是 true,排在最后。

为 false 的用于处理带 @ModelAttribute 注解的参数,为 true 的用于处理 POJO 类型的参数。

自定义参数解析器 - 重点

正常的场景下,我们都是通过自定义 converter 来自定义参数解析的过程,但是依然会有使用自定义参数解析器的场景,这里我们来尝试一下。

前面我们通过知道,通过 ServletModelAttributeMethodProcessor,可以在控制器方法中自定义 POJO 参数(对应《SpringMVC- 第三篇:在控制器方法中获取请求参数和构建响应.md》中的 直接用POJO(JavaBean)来接受参数 小节中提到的方式),这些数据都是从请求参数中来的,现在,我们想让每一个控制器方法都支持一个全局的公共参数对象,这个参数中保存的是一些全局信息(不是从请求参数中来),比如系统属性,而且这个参数在多个控制器方法中都是唯一的。也就是单例模式,在其中一个请求中对参数进行了修改,在另一个请求中也可以获取到这个修改,现在我们来试一下。

其实严格来说,这个需求可以通过在需要同步的所有控制器方法所在的 @Controller 类中都注入一个 bean 来实现,然后在所有控制器方法都调用这个 bean 即可

通过自定义参数解析器的唯一便利之处可能就是简洁和灵活。

首先创建参数类 SystemPublicArgument

public class SystemPublicArgument {

    private static volatile SystemPublicArgument instance;

    private SystemPublicArgument() {
    }

    // 单例模式
    public static SystemPublicArgument getInstance() {
        if (instance == null) {
            synchronized (SystemPublicArgument.class) {
                if (instance == null) {
                    instance = new SystemPublicArgument();
                }
            }
        }
        return instance;
    }

    // 用于计数
    private volatile int count;

    public int getCount() {
        return count;
    }

    public synchronized void increase() {
        count++;
    }

}

然后创建自定义的参数解析器 SystemInfoArgumentResolver,解析的过程也很简单,直接返回单例实例。

public class SystemInfoArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 仅支持 SystemPublicArgument 类型的参数
        Class<?> paramType = parameter.getParameterType();
        return SystemPublicArgument.class.isAssignableFrom(paramType);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // 解析过程很简单,直接获取实例,然后返回实例
        SystemPublicArgument instance = SystemPublicArgument.getInstance();
        return instance;
    }
}

然后将此参数解析器注册到系统的参数解析器中,很简单,直接在 WebMvcConfigurer 的实现类中重写 addArgumentResolvers 方法,添加参数解析器实例即可。

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    // 直接添加自定义的参数解析器即可
    resolvers.add(new SystemInfoArgumentResolver());
}

此时,参数解析器已经进入了 RequestMappingHandlerAdapter 的参数解析器列表中

而且可以注意到,SystemInfoArgumentResolver 排在 ServletModelAttributeMethodProcessor 的前面,即,所有的自定义参数解析器的优先级都是比 ServletModelAttributeMethodProcessor 高的,因此我们不必担心自定义的参数解析器无法覆盖默认的参数解析器的问题。

编写控制器方法,并使用这个参数。

@Controller
@RequestMapping("/argumentResolver")
public class ArgumentResolverController {

    @GetMapping("/count")
    @ResponseBody
    public String testSystemPublicArgument(HttpServletRequest request, SystemPublicArgument publicArgument) {
        // 业务很简单,增加计数,然后返回。
        publicArgument.increase();
        return publicArgument.getCount() + "";
    }

}

启动项目之后直接访问 http://localhost:8080/SpringMVC_AnnotationConfig/argumentResolver/count

第一次访问返回 1,

第二次访问返回 2,

第三次访问返回 3

以此类推。

我们也可以通过单元测试来进行测试,结果是一样的。

@SpringJUnitWebConfig(WebConfig.class)
class ArgumentResolverControllerTest {
    MockMvc mockMvc;
    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    void testSystemPublicArgument() throws Exception {
        // 连续调用三次
        // 第一次返回1
        mockMvc.perform(get("/argumentResolver/count")).andDo(print());
        // 第二次返回2
        mockMvc.perform(get("/argumentResolver/count")).andDo(print());
        // 第三次返回3
        mockMvc.perform(get("/argumentResolver/count")).andDo(print());
    }
}

返回值处理器

返回值解析器的作用是就是解析控制器方法的返回值,并将结果设置到 ModelAndViewContainer 中,简而言之就是将返回值解析成 ModelAndView 对象(设置 model 和视图名称),我们在编写控制器方法的时候,能编写哪些类型的返回值,实际上就取决于当前容器中有哪些返回值处理器

Spring 的控制器方法默认支持哪些返回值类型,看官方文档:Spring MVC Return Values

返回值类型为 SseEmitter 或者 ResponseBodyEmitter 的场景及代码实践,请看《SSE 简单实践》

初始化

我们有两种初始化 RequestMappingHandlerAdapter 中的返回值处理器的方式,

在前面对 RequestMappingHandlerAdapterafterPropertiesSet 方法的解析可知,如果 returnValueHandlers 为 null(说明未调用 RequestMappingHandlerAdapter#setReturnValueHandlers 设置返回值处理器),则调用 RequestMappingHandlerAdapter#getDefaultReturnValueHandlers 对其进行初始化,其中包含了 RequestMappingHandlerAdapter#setCustomReturnValueHandlers 添加的自定义返回值处理器:

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);

    // 单一用途的返回值类型
    // ModelAndView
    handlers.add(new ModelAndViewMethodReturnValueHandler());
    // Model
    handlers.add(new ModelMethodProcessor());
    // View
    handlers.add(new ViewMethodReturnValueHandler());
    // ResponseBodyEmitter
    handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
    // StreamingResponseBody
    handlers.add(new StreamingResponseBodyReturnValueHandler());
    // HttpEntity
    handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
    // HttpHeaders
    handlers.add(new HttpHeadersReturnValueHandler());
    // Callable
    handlers.add(new CallableMethodReturnValueHandler());
    // DeferredResult ListenableFuture CompletionStage
    handlers.add(new DeferredResultMethodReturnValueHandler());
    // WebAsyncTask
    handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

    // 注解修饰的返回值类型
    // @ModelAttribute注解
    handlers.add(new ServletModelAttributeMethodProcessor(false));
    // @ResponseBody注解
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));

    // 多种用途的返回值类型
    // void 或者 CharSequence(比如String)
    handlers.add(new ViewNameMethodReturnValueHandler());
    // Map
    handlers.add(new MapMethodProcessor());

    // 自定义返回值类型的返回值处理器
    if (getCustomReturnValueHandlers() != null) {
        handlers.addAll(getCustomReturnValueHandlers());
    }

    // 兜底的返回值处理器
    if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
        handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
    }
    else {
        handlers.add(new ServletModelAttributeMethodProcessor(true));
    }

    return handlers;
}

抛开自定义的返回值处理器,Spring 内置的返回值处理器有 15 个。

生效

RequestMappingHandlerAdapter#invokeHandlerMethod 可知,将其设置到 ServletInvocableHandlerMethod 实例中的 returnValueHandlers 属性中,在调用 ServletInvocableHandlerMethod#invokeAndHandle 的时候,先调用 invokeForRequest 方法进行请求处理,返回的值,会通过 returnValueHandlers 属性,进行返回值处理。很简单。具体细节请看此篇文章前面提到的 ServletInvocableHandlerMethod

HandlerMethodReturnValueHandler

这是一个策略接口,用于处理从 handler 返回的值。

只有两个方法:

子类实现:

ViewNameMethodReturnValueHandler

这是最常规最常用的返回值解析器,用于处理 void 和 String 类型的返回值,直接将 String 类型的返回值设置为视图名称,然后进行相应的视图解析。void 类型的返回值会进行默认的视图名解析,

关于默认的视图名称解析的细节,请看《SpringMVC- 第五篇:视图.md》的 默认视图解析 小节

从 Spring 4.2 开始,它还处理一般的 CharSequence 类型,例如 StringBuilder 或 Groovy 的 GString,作为视图名。

同时,如果控制器方法上存在 @ModelAttribute@ResponseBody 注解,那么 String 类型的返回值的解析方式就会不一样,ViewNameMethodReturnValueHandler 应该配置在支持这些注解的返回值处理器之后,实际上也是如此,ViewNameMethodReturnValueHandler 刚好就排在 ServletModelAttributeMethodProcessorRequestResponseBodyMethodProcessor。(所有顺序参考 RequestMappingHandlerAdapter#getDefaultReturnValueHandlers 方法)

直接看 supportsReturnType 方法的实现

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    Class<?> paramType = returnType.getParameterType();
    // 支持void 类型和 CharSequence 类型,比如其子类String
    return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}

再看 handleReturnValue 方法的实现

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    // 返回值是字符串
    if (returnValue instanceof CharSequence) {
        String viewName = returnValue.toString();
        // 直接将字符串设置为视图名
        mavContainer.setViewName(viewName);
        // 如果是转发视图,则进行标记
        if (isRedirectViewName(viewName)) {
            mavContainer.setRedirectModelScenario(true);
        }
    }
    // 要么是字符串,要么是 void ,如果还有第三种情况,直接报错
    else if (returnValue != null) {
        // should not happen
        throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
    }
}

实际上 handleReturnValue 方法能做的,也就是设置 mavContainer,设置必要的 model 属性和视图名称,如果不设置视图名称,则会进行默认的视图名解析

关于默认的视图名称解析的细节,请看《SpringMVC- 第五篇:视图.md》的 默认视图解析 小节

RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor 的作用是,解析 @RequestBody 注解修饰的控制器方法参数,同时处理 @ResponseBody 注解修饰的控制器方法的返回值,即它既是一个参数解析器,也是一个返回值处理器,具体的做法是,通过 HttpMessageConverter 来读取请求的消息体的内容和将内容写入到响应的消息体中。在使用 HttpMessageConverter 的过程中,就会进行内容协商

关于 @RequestBody 注解和 @ResponseBody 注解的应用,请看《SpringMVC- 第三篇:在控制器方法中获取请求参数和构建响应.md》中的 通过 @RequestBody 注解 小节和 @ResponseBody注解 小节。

关于内容协商,请看《SpringMVC-ContentNegotiation 内容协商.md

我们来简单看源码:

RequestResponseBodyMethodProcessor 的构造函数,主要是传入三个参数,

RequestResponseBodyMethodProcessor 是一个参数解析器,支持解析所有 @RequestBody 注解修饰的控制器方法参数,RequestResponseBodyMethodProcessor 同时也是一个返回值解析器,支持 @ResponseBody 注解修饰的返回值类型

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

    // 省略其他构造函数
    ......

    // RequestResponseBodyMethodProcessor 的基础构造函数 ,其实本质上还是调用父类的构造函数
    // 主要是传入三个参数, HttpMessageConverter 列表,ContentNegotiationManager ,这两个都是为了内容协商
    // requestResponseBodyAdvice这个参数在《SpringMVC-第八篇:跨控制器间共享.md》的 @ModelAttribute方法和@InitBinder方法 小节中提到过
    public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
        super(converters, manager, requestResponseBodyAdvice);
    }


    // RequestResponseBodyMethodProcessor 是一个参数解析器,支持解析所有 @RequestBody 注解修饰的控制器方法参数
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

    // RequestResponseBodyMethodProcessor 同时也是一个返回值解析器 支持 @ResponseBody 注解修饰的返回值类型
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        parameter = parameter.nestedIfOptional();
        // 调用 readWithMessageConverters,其实也要通过 MessageConverter 来进行请求参数的解析
        // 也就是说 HttpMessageConverter 、 内容协商,不仅会用在 返回值解析中(@ResponseBody注解修饰),也会用在请求参数解析中(@RequestBody注解修饰)
        // 具体请查看相关章节 《SpringMVC-ContentNegotiation内容协商.md》的 HttpMessageConverter 下的 主要应用场景 小节
        // 这里就不多深入研究了
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
            if (arg != null) {
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                }
            }
            if (mavContainer != null) {
                mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
            }
        }

        return adaptArgumentIfNecessary(arg, parameter);
    }

    // readWithMessageConverters 的相关方法
    ......


    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

        // 简单设置 requestHandled 这个标志位
        // requestHandled这个标志位表示,请求是否已经在handler中被完全处理,例如被@ResponseBody注解修饰的handler,如果requestHandled为true,则表示请求在handler中已经被完全处理,因此不再需要进行视图解析。
        // 当控制器方法声明ServletResponse或OutputStream类型的参数时,也可以设置此标志。
        mavContainer.setRequestHandled(true);
        // 从 NativeWebRequest 中获取 HttpServletRequest 包装成 ServletServerHttpRequest
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        // 从 NativeWebRequest 中获取 HttpServletResponse 包装成 ServletServerHttpResponse
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

        // Try even with null return value. ResponseBodyAdvice could get involved.
        // 调用父类 AbstractMessageConverterMethodProcessor 的  writeWithMessageConverters 方法进行内容协商,并写入响应
        // 具体请看 在《SpringMVC-ContentNegotiation内容协商.md》的`简单分析RequestResponseBodyMethodProcessor`小节
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }

}

跟参数解析功能相关的代码这里我们先不仔细研究了,先看看返回值处理相关的源码,我们直接看 handleReturnValue 方法。

最终是调用了父类 AbstractMessageConverterMethodProcessorwriteWithMessageConverters 方法进行内容协商,并写入响应,关于此方法的具体源码分析,请看在《SpringMVC-ContentNegotiation 内容协商.md》的 简单分析RequestResponseBodyMethodProcessor 小节。

自定义返回值解析器 - 重点

返回值解析器的功能实际上就是通过返回值,确定最终的 model 属性和视图名称,用于后续的视图解析。

我们希望通过返回值处理器达到这样一个效果,返回特定类型的返回值的时候,进入特定的视图,并展示控制器方法的返回值的相关信息。

话说回来,我这个例子写的挺鸡肋的,手动笑哭

首先创建我们要特殊处理的返回值类型 InfoPanel

public class InfoPanel {

    private String name;

    private int count;

    private String description;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

然后创建自定义的返回值解析器 InfoPanelHandler,返回值处理的逻辑也很简单,直接设置视图名为固定视图名,同时将控制器方法返回值放入 model 域中,以在视图中进行展示。

public class InfoPanelHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        // 仅支持 InfoPanel 类型的参数
        Class<?> paramType = returnType.getParameterType();
        return  InfoPanel.class.isAssignableFrom(paramType);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 将控制器方法的返回值直接放入model中
        mavContainer.getModel().addAttribute("info", returnValue);
        // 视图名为固定的视图名
        mavContainer.setViewName("infoPanelView");
    }
}

创建视图 infoPanelView.html 展示 InfoPanel

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>展示</title>
</head>
<body>
<p th:text="${info.name}"></p>
<p th:text="${info.count}"></p>
<p th:text="${info.description}"></p>
</body>
</html>

然后将此返回值解析器注册到系统的返回值解析器中,很简单,直接在 WebMvcConfigurer 的实现类中重写 addReturnValueHandlers 方法,添加返回值解析器实例即可。

@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
    // 直接添加自定义的返回值处理器即可
    handlers.add(new InfoPanelHandler());
}

此时,返回值解析器已经进入了 RequestMappingHandlerAdapter 的返回值解析器列表中

然后编写控制器方法,并返回此类型的返回值。

@Controller
@RequestMapping("/returnValueHandler")
public class ReturnValueHandlerController {

    @GetMapping("/info")
    public InfoPanel testSystemPublicArgument(HttpServletRequest request) {
        InfoPanel info = new InfoPanel();
        info.setName("xiashuo123");
        info.setCount(100);
        info.setDescription("测试456");
        return info;
    }

}

在浏览器中访问 http://localhost:8080/SpringMVC_AnnotationConfig/returnValueHandler/info,结果正常渲染。