小架构step系列21:参数和返回值的匹配

1 概述

从前面看到参数是通过HandlerMethodArgumentResolver进行匹配的,参数有各种各样的形式,比如带注解的、自定义类、基本类型、甚至ServletReqest之类的。返回值则是通过ReturnValueHandler来处理的,返回值也有比较多类型,比如String、Void、Model、自定义类等,如果不是前后端分离的,还可能带View之类的。本文了解一下参数和返回值的匹配原理。

2 参数匹配

2.1 参数匹配原理

遍历所有HandlerMethodArgumentResolver,如果Resolver的supportsParameter()接口返回值为true,则匹配此Resolver,不再看下面其它的Resolver。

java 复制代码
// 源码位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        // 遍历所有argumentResolver,调用其supportsParameter()方法,如果返回值为true则匹配上此argumentResolver
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

默认有27个Resolver:

java 复制代码
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    resolvers.add(new ServletModelAttributeMethodProcessor(false));
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new RequestHeaderMapMethodArgumentResolver());
    resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new SessionAttributeMethodArgumentResolver());
    resolvers.add(new RequestAttributeMethodArgumentResolver());

    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    resolvers.add(new ModelMethodProcessor());
    resolvers.add(new MapMethodProcessor());
    resolvers.add(new ErrorsMethodArgumentResolver());
    resolvers.add(new SessionStatusMethodArgumentResolver());
    resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
    if (KotlinDetector.isKotlinPresent()) {
        resolvers.add(new ContinuationHandlerMethodArgumentResolver());
    }

    // 注意自定义的ArgumentResolver位置
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    resolvers.add(new PrincipalMethodArgumentResolver());
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
    resolvers.add(new ServletModelAttributeMethodProcessor(true));

    return resolvers;
}

org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver
org.springframework.web.method.annotation.ModelMethodProcessor
org.springframework.web.method.annotation.MapMethodProcessor
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor

注意:RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor有两个。

2.2 常用的MethodArgumentResolver

下面列一下几个常用的MethodArgumentResolver:

java 复制代码
// 源码位置:org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
    // 参数前加了@RequestParam注解时,用此MethodArgumentResolver处理
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        // 如果参数是Map类型,则还需要在@RequestParam注解中指定映射字段名
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
            return (requestParam != null && StringUtils.hasText(requestParam.name()));
        }
        else {
            return true;
        }
    }
    else {
        // 参数前加了@RequestPart注解时,不用此MethodArgumentResolver处理
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        }
        parameter = parameter.nestedIfOptional();
        // 参数类型为MultipartFile或Part时(可以List里放这些类型),用此MethodArgumentResolver处理
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        }
        else if (this.useDefaultResolution) {
            // SpringMVC准备了两个RequestParamMethodArgumentResolver,一个useDefaultResolution=false,另外一个useDefaultResolution=true(倒数第二个Resolver)
            // 如果useDefaultResolution=true,则普通类型如Void、int/byte/long等基础类型可以用此MethodArgumentResolver处理
            // 详细类型参考BeanUtils.isSimpleProperty()方法
            return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
        }
        else {
            return false;
        }
    }
}
// 源码位置:org.springframework.beans.BeanUtils
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));
}


// 源码位置:org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
    // 参数前加了@RequestParam注解、且参数类型为Map类型、也没有在@RequestParam注解指定字段映射名时,用此MethodArgumentResolver处理
    RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
    return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
            !StringUtils.hasText(requestParam.name()));
}

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
    // 参数前没有加@PathVariable注解,不用此MethodArgumentResolver处理
    if (!parameter.hasParameterAnnotation(PathVariable.class)) {
        return false;
    }
    // 参数前加了@PathVariable注解,且参数为Map类型时,需要在注解指定字段映射名,才用此MethodArgumentResolver处理
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
        PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
        return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
    }
    return true;
}

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsParameter
public boolean supportsParameter(MethodParameter parameter) {
    // 参数前加了@RequestBody注解,用此MethodArgumentResolver处理
    return parameter.hasParameterAnnotation(RequestBody.class);
}

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
// 实际由父类org.springframework.web.method.annotation.ModelAttributeMethodProcessor提供supportsParameter()方法
public boolean supportsParameter(MethodParameter parameter) {
    // 两种情况用此MethodArgumentResolver处理:
    // 1. 参数前加了@ModelAttribute注解;
    // 2. 没有加参数注解,参数未非普通类型(普通类型参考上面BeanUtils.isSimpleProperty()的实现);
    // 注:有两个ServletModelAttributeMethodProcessor,一个annotationNotRequired=false,另一个annotationNotRequired=true(倒数第一个Resolver)
    return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
            (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
    // 参数是Servlet有关的类型,用此MethodArgumentResolver处理
    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);
}

从上面可以看到如果有注解则优先匹配注解(如@RequestParam、@RequestPart、@RequestBody)。
在没有注解的时候,主要由RequestParamMethodArgumentResolver、ServletModelAttributeMethodProcessor、ServletRequestMethodArgumentResolver、自定义Resolver处理。

  • 在Resolver列表的排序是:ServletRequestMethodArgumentResolver、自定义Resolver、RequestParamMethodArgumentResolver、ServletModelAttributeMethodProcessor。
  • RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor有两个,它们各自的第二个都置标记为true,才会处理没有注解的情况。
  • ServletRequestMethodArgumentResolver匹配的是Servlet有关的类型,如ServletRequest、InputStream等;
  • RequestParamMethodArgumentResolver匹配的是普通类型,如Void、Number、int/String基本类型等;
  • ServletModelAttributeMethodProcessor匹配的是非普通类型,也就是上面没匹配到的所有类型;

2.3 应用

在请求的时候,一般请求参数有两种形式:

  • Form表单形式:一个字段一个字段分开的方式,字段可以是对象结构,大部分ArgumentResolver都是用来匹配这种形式的。
  • JSON数据形式:所有数据封装到一个JSON对象当中,要匹配这种形式的参数,参数前必须加@RequestBody注解,由RequestResponseBodyMethodProcessor匹配。
    如果强制请求参数用JSON数据形式,又不希望必须在参数前加@RequestBody注解(可能忘掉),则需要自行加一个自定义的ArgumentResolver,把所有情况都当JSON数据处理。

3 返回值匹配

3.1 返回值匹配原理

遍历所有ReturnValueHandler,如果Resolver的supportsReturnType()接口返回值为true,则匹配此Handler,不再看下面其它的Handler。

java 复制代码
// 源码位置:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        // 遍历所有支持的ReturnValueHandler,调用supportsReturnType()接口,如果返回值为true,则选中此ReturnValueHandler
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

默认提供了15个ReturnValueHandler:

java 复制代码
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);

    handlers.add(new ModelAndViewMethodReturnValueHandler());
    handlers.add(new ModelMethodProcessor());
    handlers.add(new ViewMethodReturnValueHandler());
    handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
            this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
    handlers.add(new StreamingResponseBodyReturnValueHandler());
    handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
            this.contentNegotiationManager, this.requestResponseBodyAdvice));
    handlers.add(new HttpHeadersReturnValueHandler());
    handlers.add(new CallableMethodReturnValueHandler());
    handlers.add(new DeferredResultMethodReturnValueHandler());
    handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
    handlers.add(new ServletModelAttributeMethodProcessor(false));
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
            this.contentNegotiationManager, this.requestResponseBodyAdvice));
    handlers.add(new ViewNameMethodReturnValueHandler());
    handlers.add(new MapMethodProcessor());

    // 注意自定义的ReturnValueHandler的位置
    if (getCustomReturnValueHandlers() != null) {
        handlers.addAll(getCustomReturnValueHandlers());
    }

    if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
        handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
    }
    else {
        handlers.add(new ServletModelAttributeMethodProcessor(true));
    }

    return handlers;
}

org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler
org.springframework.web.method.annotation.ModelMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
org.springframework.web.method.annotation.MapMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor

3.2 常用的ReturnValueHandler

下面列一下几个常用的ReturnValueHandler:

java 复制代码
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {
    // 返回值类型为ModelAndView,用此ReturnValueHandler
    return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}

// 源码位置:org.springframework.web.method.annotation.ModelMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {
    // 返回值类型为Model,用此ReturnValueHandler
    return Model.class.isAssignableFrom(parameter.getParameterType());
}

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
public boolean supportsReturnType(MethodParameter returnType) {
    // 返回值对象对应的类指定了@ResponseBody注解,或者返回值有指定@ResponseBody注解,用此ReturnValueHandler
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {
    // 返回值类型为View,用此ReturnValueHandler
    return View.class.isAssignableFrom(returnType.getParameterType());
}

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {
    Class<?> paramType = returnType.getParameterType();
    // 如果没有返回值或者返回值是字符串类型,则用此ReturnValueHandler
    return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}

// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
// 实际用父类org.springframework.web.method.annotation.ModelAttributeMethodProcessor的实现
public boolean supportsReturnType(MethodParameter returnType) {
    // 返回值类型为ModelAttribute,或者在annotationNotRequired=true的时候不是普通类型
    // ServletModelAttributeMethodProcessor有两个,第二个(在列表最后一个)annotationNotRequired=true
    return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
            (this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
}

ReturnValueHandler的匹配逻辑是比较清晰的,大部分都是按返回值类型,还有一种是按@ResponseBody、@ModelAttribute等注解。

  • 对于普通类型只能匹配Void和CharSequence,其它的如Number、Enum等是匹配不到的,会抛IllegalArgumentException("Unknown return value type: ")异常。
  • 如果是自定义的类型,则由ServletModelAttributeMethodProcessor来匹配,注意它也有两个,只有第二个才是匹配自定义类型的。
  • 也可以自定义ReturnValueHandler来匹配其它类型,此时会排在第二个ServletModelAttributeMethodProcessor前面,其它Handler的后面。

3.3 应用

返回值一般有带视图View和纯数据的两种方式:

  • 带视图View的方式是在服务器端渲染界面的方式,返回值类型指定为ModelAndView、View、字符串等类型;
  • 纯数据一般用JSON格式,此时需要由ServletModelAttributeMethodProcessor处理,即指定@ModelAttribute注解或自定义类型;

4 架构一小步

在前后端分离的情况下,可以选择两种规范:
1、数据格式方面
方案一:参数和返回值都强制统一用JSON数据格式;好处是统一,缺点是需要自定义一下MethodArgumentResolver;
方案二:参数用Form表单格式,返回值用JSON数据格式;好处是不需要自定义MethodArgumentResolver,缺点是参数和返回值需要分开理解,处理方式不统一;
2、在使用注解方面
方案一:所有参数不用注解;好处是不会出现漏注解的情况,缺点是需要JSON格式数据的适配;
方案二:所有参数都明确注解;好处是注解清晰不用自定义,缺点是容易漏标注解;

相关推荐
一碗绿豆汤3 分钟前
JAVA+AI教程-第三天
java·spring
AI、少年郎11 分钟前
从 C# 到 Python:项目实战第五天的飞跃
开发语言·数据库·c#
hnlgzb22 分钟前
kotlin和Jetpack Compose对于Android系统来说是什么关系?
android·开发语言·kotlin
典孝赢麻崩乐急28 分钟前
Java学习 ------BIO模型
java·开发语言·学习
谢平康29 分钟前
支持不限制大小,大文件分段批量上传功能(不受nginx /apache 上传大小限制)
java·vue.js·spring boot
期待のcode29 分钟前
java内存图
java·开发语言
anan46641 分钟前
开发中的接口调试难题?Pretender-Proxy 来帮你解决
java
88号技师1 小时前
2025年一区SCI-回旋镖气动椭圆优化算法Boomerang Aerodynamic Ellipse-附Matlab免费代码
开发语言·人工智能·算法·matlab·优化算法
盖世英雄酱581361 小时前
定时任务框架原理剖析、对比、选型
java·后端
weixin_552444201 小时前
【Deepseek】RAG 技术与模型架构的创新变革
架构