SpringMVC原理(6)-目标方法执行前的参数处理

SpringMVC原理(5)-目标方法的执行 - 掘金这篇中分析了目标方法的执行流程。

现在来具体分析一下目标方法执行前的参数处理,也就是参数解析、参数绑定、参数类型转换的原理

我们在接口方法上写了参数,怎么就能获取到请求中参数的值?比如这样

java 复制代码
 @GetMapping("/hello")
 @ResponseBody
 public String hello(@RequestParam(name = "name") String name) {
     return "Hello " + name;
 }

getMethodArgumentValues()

在上一篇中已经分析过流程了,这一篇不再分析,只分析具体原理。

执行目标方法前,会调用**getMethodArgumentValues()** 获取参数的值。这个就是核心方法了

java 复制代码
 protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
                                            Object... providedArgs) throws Exception {
 
     // 1、获取目标方法所有的参数
     MethodParameter[] parameters = getMethodParameters();
     if (ObjectUtils.isEmpty(parameters)) {
         return EMPTY_ARGS;
     }
 
     Object[] args = new Object[parameters.length];
     // 2、遍历目标方法所有的参数
     for (int i = 0; i < parameters.length; i++) {
         MethodParameter parameter = parameters[i];
         parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
         args[i] = findProvidedArgument(parameter, providedArgs);
         if (args[i] != null) {
             continue;
         }
         // 3、使用参数解析器解析判断是否支持解析这种参数
         if (!this.resolvers.supportsParameter(parameter)) {
             throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
         }
         try {
             // 4、使用参数解析器来解析参数
             args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
         }
         catch (Exception ex) {
             // 5、如果有异常
             // 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;
 }

其实核心就是利用各种参数解析器来解析参数的值,所以我们把参数解析器搞明白就知道了。

默认有27个参数解析器。这里有什么参数解析器,也就意味着我们方法上就能写什么类型的参数

参数解析器-HandlerMethodArgumentResolver

java 复制代码
 public interface HandlerMethodArgumentResolver {
 
     // 判断是否支持处理此类型的参数
     boolean supportsParameter(MethodParameter parameter);
 
     // 解析参数的值
     @Nullable
     Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
             NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
 }
  • supportsParameter()判断是否支持处理此类型的参数,如果支持那就用这个参数解析器来处理
  • resolveArgument()解析参数的值

里面就这两个方法。现在我们知道了参数解析器的结构,让我们一起来看看它是怎么处理的吧。

下面分析5种情况:

  1. 加了@RequestParam的普通类型参数
  2. 加了@PathVariable的参数
  3. 自定义的对象类型参数
  4. 加了@RequestBody注解的参数
  5. 文件上传请求类型的参数

@RequestParam

会被RequestParamMethodArgumentResolver类来解析。

supportsParameter()

java 复制代码
 public boolean supportsParameter(MethodParameter parameter) {
     // 方法参数是否被 @RequestParam 注解修饰
     if (parameter.hasParameterAnnotation(RequestParam.class)) {
         if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
             RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
             return (requestParam != null && StringUtils.hasText(requestParam.name()));
         }
         else {
             // 返回true
             return true;
         }
     }
     
     // 省略代码
     ...
 }

resolveArgument()

来到父类AbstractNamedValueMethodArgumentResolver

java 复制代码
 public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                     NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
 
     // 1、构造对象,包括注解配置的 name、required、默认值 这3个属性的值
     NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
     MethodParameter nestedParameter = parameter.nestedIfOptional();
 
     // 2、得到参数名。也可以写占位符、SpEL表达式
     Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
     if (resolvedName == null) {
         throw new IllegalArgumentException(
             "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
     }
 
     // 3、解析参数的值
     Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
     // 4、请求没有传递值
     if (arg == null) {
         // 拿到我们注解配置的值
         if (namedValueInfo.defaultValue != null) {
             arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
         }
         // 处理 @RequestParam注解属性 required=true 的情况
         else if (namedValueInfo.required && !nestedParameter.isOptional()) {
             // 处理没有值的情况(抛出异常)
             handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
         }
         // 处理null值情况
         arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
     }
     // 5、值是空字符串,并且注解中配置了默认值
     else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
         arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
     }
 
     // 6、数据绑定器工厂
     if (binderFactory != null) {
         // 6.1、通过数据绑定器工厂创建出一个数据绑定器
         WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
         try {
             // 6.2、绑定器通过 Converter 来转换数据类型(如果需要的话)
             arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
         }
         catch (ConversionNotSupportedException ex) {
             throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                                                                     namedValueInfo.name, parameter, ex.getCause());
         }
         catch (TypeMismatchException ex) {
             throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                                                           namedValueInfo.name, parameter, ex.getCause());
         }
     }
 
     // 7、这里是个空方法
     handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
 
     return arg;
 }
  1. 构造一个NamedValueInfo对象。里面包括了@RequestParam中的namerequireddefaultValue属性的值
  1. 调用resolveEmbeddedValuesAndExpressions()得到参数名。也可以写占位符、SpEL表达式

  2. Object arg = resolveName():根据目标方法的参数名来解析请求参数的值

  3. 如果解析出来的值是null,也就是请求没有传递值

    1. 注解defaultValue属性的值不为null,就拿到它值
    2. 否则如果required=true,就直接抛出异常了
    3. 再然后处理null值情况
  4. 如果解析出来的是空字符串 "",并且注解中配置了默认值,就拿到注解中defaultValue属性的值

  5. 如果WebDataBinderFactory(数据绑定器工厂)不为null,就会创建一个WebDataBinder数据绑定器

  6. 通过WebDataBinder数据绑定器 )的convertIfNecessary()来进行数据类型转换

    1. 它又通过Converter转换器进行数据类型转换
    2. 比如String想要转Integer,String想要转Long,String转枚举,Long转日期
  7. handleResolvedValue():这里是个空方法

3、 根据目标方法的参数名来解析请求参数的值-resolveName()

java 复制代码
 protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
     // 1、获取原生请求
     HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
 
     if (servletRequest != null) {
         // 文件上传请求
         Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
         if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
             return mpArg;
         }
     }
 
     Object arg = null;
     // 文件上传请求
     MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
     if (multipartRequest != null) {
         List<MultipartFile> files = multipartRequest.getFiles(name);
         if (!files.isEmpty()) {
             arg = (files.size() == 1 ? files.get(0) : files);
         }
     }
     if (arg == null) {
         // 调用底层api得到请求中参数的值
         String[] paramValues = request.getParameterValues(name);
         if (paramValues != null) {
             // 单个 or 集合
             arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
         }
     }
     return arg;
 }

还会用Converter来转换数据类型,在最后总结的时候说原理。

@PathVariable

会被PathVariableMethodArgumentResolver类解析

supportsParameter()

java 复制代码
 @Override
 public boolean supportsParameter(MethodParameter parameter) {
     // 方法参数是否被 @PathVariable 注解修饰
     if (!parameter.hasParameterAnnotation(PathVariable.class)) {
         return false;
     }
     if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
         PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
         return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
     }
     return true;
 }

resolveArgument()

还是来到父类AbstractNamedValueMethodArgumentResolver中,逻辑和上面的一样,只是具体实现类变了。

根据目标方法的参数名来解析请求参数的值-resolveName()

typescript 复制代码
 protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
     Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
         HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
     return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
 }
 
 public Object getAttribute(String name, int scope) {
     if (scope == SCOPE_REQUEST) {
         if (!isRequestActive()) {
             throw new IllegalStateException(
                 "Cannot ask for request attribute - request is not active anymore!");
         }
         // 调用底层api从请求域中获取数据
         return this.request.getAttribute(name);
     }
     else {
         HttpSession session = getSession(false);
         if (session != null) {
             try {
                 Object value = session.getAttribute(name);
                 if (value != null) {
                     this.sessionAttributesToUpdate.put(name, value);
                 }
                 return value;
             }
             catch (IllegalStateException ex) {
                 // Session invalidated - shouldn't usually happen.
             }
         }
         return null;
     }
 }

最后也会调用Converter进行数据类型转换

自定义的对象参数

会被ServletModelAttributeMethodProcessor这个类处理

supportsParameter()

typescript 复制代码
 // 不是必须的,并且不是简单类型
 public boolean supportsParameter(MethodParameter parameter) {
     return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
             (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
 }
 
 // 简单类型有这些
 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()

java 复制代码
 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");
 
     // 参数名字
     String name = ModelFactory.getNameForParameter(parameter);
     // @ModelAttribute注解的处理,这里不用管
     ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
     if (ann != null) {
         mavContainer.setBinding(name, ann.binding());
     }
 
     Object attribute = null;
     BindingResult bindingResult = null;
 
     if (mavContainer.containsAttribute(name)) {
         attribute = mavContainer.getModel().get(name);
     }
     else {
         // Create attribute instance
         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();
             }
             bindingResult = ex.getBindingResult();
         }
     }
 
     // 
     if (bindingResult == null) {
         // 创建一个Web数据绑定器,里面封装了刚创建的Java对象
         WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
         if (binder.getTarget() != null) {
             if (!mavContainer.isBindingDisabled(name)) {
                 // 绑定请求参数到Java对象上
                 bindRequestParameters(binder, webRequest);
             }
             // @Validated注解校验逻辑
             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
     Map<String, Object> bindingResultModel = bindingResult.getModel();
     mavContainer.removeAttributes(bindingResultModel);
     mavContainer.addAllAttributes(bindingResultModel);
 
     // 把封装号的对象返回
     return attribute;
 }

流程:

  1. 获取参数名字

  2. @ModelAttribute注解的处理

  3. createAttribute():创建目标方法参数的对象,用来封装请求携带的参数值

  4. bindRequestParameters():使用WebDataBinder来绑定数据

bindRequestParameters()方法

ServletModelAttributeMethodProcessor

java 复制代码
 protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
     ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
     Assert.state(servletRequest != null, "No ServletRequest");
     ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
     // 绑定数据
     servletBinder.bind(servletRequest);
 }

bind()方法

ServletRequestDataBinder

java 复制代码
 public void bind(ServletRequest request) {
     // 获取请求参数的值封装为MutablePropertyValues对象
     MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
     // 文件上传请求
     MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
     if (multipartRequest != null) {
         bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
     }
     addBindValues(mpvs, request);
     // 绑定
     doBind(mpvs);
 }

请求参数会被封装到MutablePropertyValues对象中:

doBind()

java 复制代码
 // ===WebDataBinder.class===
 protected void doBind(MutablePropertyValues mpvs) {
     checkFieldDefaults(mpvs);
     checkFieldMarkers(mpvs);
     super.doBind(mpvs);
 }
 
 // ===DataBinder.class===
 protected void doBind(MutablePropertyValues mpvs) {
     checkAllowedFields(mpvs);
     checkRequiredFields(mpvs);
     // 应用参数的值
     applyPropertyValues(mpvs);
 }
 

 protected void applyPropertyValues(MutablePropertyValues mpvs) {
     try {
         // 绑定请求参数的值到目标对象上
         getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
     }
     catch (PropertyBatchUpdateException ex) {
         // Use bind error processor to create FieldErrors.
         for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
             getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
         }
     }
 }
 
 // ===AbstractPropertyAccessor.class===
 public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
     throws BeansException {
 
     List<PropertyAccessException> propertyAccessExceptions = null;
     List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
                                           ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
 
     if (ignoreUnknown) {
         this.suppressNotWritablePropertyException = true;
     }
     try {
         // 遍历所有的属性值,然后绑定
         for (PropertyValue pv : propertyValues) {
             // setPropertyValue may throw any BeansException, which won't be caught
             // here, if there is a critical failure such as no matching field.
             // We can attempt to deal only with less serious exceptions.
             try {
                 setPropertyValue(pv);
             }
             
             // 省略代码
             ...
         }
     }
     
     // 省略代码
     ...
 }

这一套流程走完之后,对象里就有数据了。然后在利用反射执行目标方法的时候,把这个对象传递过去就好了。

到这一步我们就知道了自定义的对象类型的参数是怎么封装的。就是会先创建一个对应的对象,然后通过WebDataBinder把参数绑定到这个对象上,并会利用Converter做类型转换。

@RequestBody

会被RequestResponseBodyMethodProcessor类处理

supportsParameter()

参数被@RequestBody注解修饰

java 复制代码
 public boolean supportsParameter(MethodParameter parameter) {
     return parameter.hasParameterAnnotation(RequestBody.class);
 }

resolveArgument()

java 复制代码
 public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                               NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
 
     parameter = parameter.nestedIfOptional();
     // 使用 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);
 }

使用HttpMessageConverter读取请求携带的参数值,它最终会利用ObjectMapperreadValue()来读取数据

通过ObjectMapper读取到的数据:

默认的HttpMessageConverter,有10

文件上传请求类型的参数

文件上传请求:

  1. 没标注解:会被RequestParamMethodArgumentResolver类处理
  2. 标了@RequestPart注解会被RequestPartMethodArgumentResolver类处理

我这里分析的是RequestParamMethodArgumentResolver

supportsParameter()

java 复制代码
 public boolean supportsParameter(MethodParameter parameter) {
     if (parameter.hasParameterAnnotation(RequestParam.class)) {
         if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
             RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
             return (requestParam != null && StringUtils.hasText(requestParam.name()));
         }
         else {
             return true;
         }
     }
     else {
         if (parameter.hasParameterAnnotation(RequestPart.class)) {
             return false;
         }
         parameter = parameter.nestedIfOptional();
         // 参数是否是 MultipartFile 类型
         if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
             return true;
         }
         else if (this.useDefaultResolution) {
             return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
         }
         else {
             return false;
         }
     }
 }
 
 // ===MultipartResolutionDelegate.class===
 public static boolean isMultipartArgument(MethodParameter parameter) {
     // 获取参数类型
     Class<?> paramType = parameter.getNestedParameterType();
     // MultipartFile或Part类型
     return (MultipartFile.class == paramType ||
             isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) ||
             (Part.class == paramType || isPartCollection(parameter) || isPartArray(parameter)));
 }

resolveArgument()

这里我只把关键的拿过来,流程都是和@RequestParam的解析原理是一样的,不知道的可以再看一下上面

java 复制代码
 protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
     // 获取原生请求对象,这里获取到的是
     HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
 
    
     if (servletRequest != null) {
         // 利用这个工具类来解析文件上传的参数
         Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
         if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
             return mpArg;
         }
     }
 
     Object arg = null;
     MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
     if (multipartRequest != null) {
         List<MultipartFile> files = multipartRequest.getFiles(name);
         if (!files.isEmpty()) {
             arg = (files.size() == 1 ? files.get(0) : files);
         }
     }
     if (arg == null) {
         String[] paramValues = request.getParameterValues(name);
         if (paramValues != null) {
             arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
         }
     }
     return arg;
 }
  1. 获取原生请求对象,这里获取到的是
  2. 利用MultipartResolutionDelegate工具类来解析文件类型的参数

工具类MultipartResolutionDelegate

java 复制代码
 public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
     throws Exception {
 
     MultipartHttpServletRequest multipartRequest =
         WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
     boolean isMultipart = (multipartRequest != null || isMultipartContent(request));
 
     // MultipartFile
     if (MultipartFile.class == parameter.getNestedParameterType()) {
         if (multipartRequest == null && isMultipart) {
             multipartRequest = new StandardMultipartHttpServletRequest(request);
         }
         return (multipartRequest != null ? multipartRequest.getFile(name) : null);
     }
     else if (isMultipartFileCollection(parameter)) {// 集合 List<MultipartFile>
         if (multipartRequest == null && isMultipart) {
             multipartRequest = new StandardMultipartHttpServletRequest(request);
         }
         return (multipartRequest != null ? multipartRequest.getFiles(name) : null);
     }
     else if (isMultipartFileArray(parameter)) { // 数组 MultipartFile[]
         if (multipartRequest == null && isMultipart) {
             multipartRequest = new StandardMultipartHttpServletRequest(request);
         }
         if (multipartRequest != null) {
             List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
             return multipartFiles.toArray(new MultipartFile[0]);
         }
         else {
             return null;
         }
     }
     else if (Part.class == parameter.getNestedParameterType()) { // Part
         return (isMultipart ? request.getPart(name): null);
     }
     else if (isPartCollection(parameter)) { // List<Part>
         return (isMultipart ? resolvePartList(request, name) : null);
     }
     else if (isPartArray(parameter)) { // Part[]
         return (isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null);
     }
     else {
         return UNRESOLVABLE;
     }
 }
  • 判断参数是否是MultipartFile,是的话就从包装好的请求中获取到上传的文件对象
  • 参数可以是:MultipartFileList<MultipartFile>MultipartFile[]PartList<Part>Part[]类型

不懂这个原理的,可以去看这篇,具体原理在SpringMVC原理(1)-文件上传请求 - 掘金中说过

总结

流程:

  1. 遍历判断哪个参数解析器能处理:supportsParameter()

  2. 调用不同的参数解析器的resolveArgument()来解析不同类型的参数

    1. 利用WebDataBinder来绑定参数
    2. 利用Converter来转换数据类型
    3. json格式利用HttpMessageConverter来读或写,它又利用jackson包的ObjectMapper类做
  3. 把参数封装好返回,最后利用反射来传递参数和执行目标方法

HandlerMethodArgumentResolver

参数解析器。

作用:用来把请求参数封装到目标方法上

  • @RequestParam注解:RequestPartMethodArgumentResolver

  • @PathVariable注解:PathVariableMethodArgumentResolver

  • @RequestBody注解:RequestResponseBodyMethodProcessor

  • 自定义JavaBean参数:ServletModelAttributeMethodProcessor

  • 文件上传请求参数:

    • 默认没标会被RequestParamMethodArgumentResolver类处理
    • 标了@RequestPart注解会被RequestPartMethodArgumentResolver类处理

WebDataBinder

默认使用ExtendedServletRequestDataBinder

两个作用

  1. 将请求参数的值绑定到指定的JavaBean里面;

  2. 调用Converter做数据类型转换

Converter

转换器。

作用:用来数据类型转换

比如把request带来参数的字符转为int、long、float

默认的Converter

原理

进行数据类型转换convertIfNecessary()

如果需要的话,利用Converter进行数据类型转换

java 复制代码
    // ======DataBinder.class==========
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
                                    @Nullable MethodParameter methodParam) throws TypeMismatchException {

        return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
    }

    // =======TypeConverterSupport.class=======
    TypeConverterDelegate typeConverterDelegate;

    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
                                    @Nullable MethodParameter methodParam) throws TypeMismatchException {

        return convertIfNecessary(value, requiredType,
                                  (methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));
    }

    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
                                    @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {

        Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
        try {
            // 通过类型转换器的委托来进行类型转换
            return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
        }
        catch (ConverterNotFoundException | IllegalStateException ex) {
            throw new ConversionNotSupportedException(value, requiredType, ex);
        }
        catch (ConversionException | IllegalArgumentException ex) {
            throw new TypeMismatchException(value, requiredType, ex);
        }
    }

> TypeConverterDelegate

    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
                                    @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
        // 省略代码
        ...
        
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                try {
                    return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                }
                catch (ConversionFailedException ex) {
                    // fallback to default conversion logic below
                    conversionAttemptEx = ex;
                }
            }
        }
        
        // 省略代码
        ...
    }

这里又通过ConversionService来做。这个类里封装了所有的Converter

  1. 遍历所有的Converter,判断是否能讲请求参数的类型转为目标方法参数的类型
  2. 调用Converterconverter()来进行类型转换

补充-集合类型

java 复制代码
    @PostMapping("/hello")
    @ResponseBody
    public List<String> postHello(@RequestParam List<String> list) {
        return list;
    }

类型转换器前:数组

调用类型转换器后:集合

使用的Converter

HttpMessageConverter

消息转换器。

作用 :用来读取json数据(@RequestBody)和响应json(@ResponseBody

原理就是利用jacksonObjectMapper类来读或写

HttpMessageConverter接口

java 复制代码
public interface HttpMessageConverter<T> {
    // 是否能读
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

    // 是否能写
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

    // 支持处理的媒体类型
    List<MediaType> getSupportedMediaTypes();

    // 读
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
        throws IOException, HttpMessageNotReadableException;

    // 写
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
        throws IOException, HttpMessageNotWritableException;

}

默认的HttpMessageConverter,有10个:

原理

java 复制代码
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
                                               Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    MediaType contentType;
    boolean noContentType = false;
    try {
        // 获取 Content-Type 的值
        contentType = inputMessage.getHeaders().getContentType();
    }
    catch (InvalidMediaTypeException ex) {
        throw new HttpMediaTypeNotSupportedException(ex.getMessage());
    }
    if (contentType == null) {
        noContentType = true;
        contentType = MediaType.APPLICATION_OCTET_STREAM;
    }

    Class<?> contextClass = parameter.getContainingClass();
    Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
    if (targetClass == null) {
        ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
        targetClass = (Class<T>) resolvableType.resolve();
    }

    HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
    Object body = NO_VALUE;

    EmptyBodyCheckingHttpInputMessage message;
    try {
        message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

        // 遍历所有HttpMessageConverter
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
            GenericHttpMessageConverter<?> genericConverter =
                (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
            // 判断是否支持读
            if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
                (targetClass != null && converter.canRead(targetClass, contentType))) {
                if (message.hasBody()) {
                    HttpInputMessage msgToUse =
                        getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                    // 读取
                    body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                            ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
                    body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                }
                else {
                    body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
                }
                break;
            }
        }
    }
    catch (IOException ex) {
        throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
    }

    if (body == NO_VALUE) {
        if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
            (noContentType && !message.hasBody())) {
            return null;
        }
        throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
    }

    MediaType selectedContentType = contentType;
    Object theBody = body;
    LogFormatUtils.traceDebug(logger, traceOn -> {
        String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
        return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
    });

    return body;
}
  1. 获取Content-Type的值
  2. 遍历所有HttpMessageConverter,挨个调用canRead()判断看谁支持读,支持的话就调用read()来读取

最终AbstractJackson2HttpMessageConverter类支持读取这种类型

kotlin 复制代码
return objectReader.readValue(inputMessage.getBody());
相关推荐
why技术28 分钟前
可以说是一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
后端·面试
m0_7482546632 分钟前
定时任务特辑 Quartz、xxl-job、elastic-job、Cron四个定时任务框架对比,和Spring Boot集成实战
java·spring boot·后端
diemeng11191 小时前
2024系统编程语言风云变幻:Rust持续领跑,Zig与Ada异军突起
开发语言·前端·后端·rust
Warren981 小时前
Springboot中分析SQL性能的两种方式
java·spring boot·后端·sql·mysql·intellij-idea
独孤求败Ace2 小时前
第46天:Web开发-JavaEE应用&原生和FastJson反序列化&URLDNS链&JDBC链&Gadget手搓
java·spring·java-ee
计算机学姐2 小时前
基于SpringBoot的校园消费点评管理系统
java·vue.js·spring boot·后端·mysql·spring·java-ee
猎人everest2 小时前
Spring Boot数据访问(JDBC)全解析:从基础配置到高级调优
java·spring boot·后端
重庆穿山甲2 小时前
Akka实战指南:高并发难题的终极解法
后端
2501_903238653 小时前
Spring MVC中ViewControllerRegistry的使用与原理
java·spring·mvc·个人开发
coderzjy3 小时前
苍穹外卖中的模块总结
spring boot·后端