Spring MVC源码分析

大彬
大约 43 分钟

上篇中我们已经讲了SpringMVC处理HTTP请求的整体流程,其间我们讲到了很多接口,如参数解析器,返回结果处理器等,但我们都没有深入的进去查看这些解析器或处理器是如何处理的。本章就带大家进入真实的案例场景,看看这些接口在具体场景下是如何发挥作用的

0. 参数解析器

为了方便后面具体案例的分析,我们先来回顾下之前在上一篇中讲到的参数解析器。

当我们发送HTTP请求 时,根据之前的框架分析,我们知道这个请求会由RequestMappingHandlerAdapter来处理,在RequestMappingHandlerAdapter来处理的时候,会将自己的很多信息封装到ServletInvocableHandlerMethod中,包括自己的参数解析器和返回值处理器。ServletInvocableHandlerMethod做处理的代码我们之前看过了,这里再拿过来:

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

    //参数解析与HandlerMethod的执行
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    //... 一些返回的处理
}

其中invokeForRequest()内容如下:

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

    //参数解析
   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    //...日志
    //函数执行
   return doInvoke(args);
}

getMethodArgumentValues()内容如下:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
    
   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);
      args[i] = findProvidedArgument(parameter, providedArgs);
      if (args[i] != null) {
         continue;
      }
       //判断解析器是否支持解析
       //本质是循环遍历
      if (!this.resolvers.supportsParameter(parameter)) {
         throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
      }
      try {
          //找到能解析的解析器就解析参数
         args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
      }
       //...异常处理
   }
   return args;
}

这里的supportsParameter()源码如下:

public boolean supportsParameter(MethodParameter parameter) {
    return getArgumentResolver(parameter) != null;
}
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;
}

resolveArgument()源码如下:

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

   HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    //异常处理
   return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

我们之前虽然大体上走完了每个流程,但其实并没有深入到每个接口的实现类里去看,这一主要原因是SpringMVC要处理的情况太多了,只能结合实际的场景来说源码。因此下面我们将会一个一个场景的讲源码处理,首先从参数解析器的源码场景说起。

1. @PathVariable

@PathVariable注解是Web开发中十分常见的一个注解,我们可以从路径中获取信息作为参数。

举个例子如下:

@RestController
public class PathVariableController {
    @GetMapping("/user/{id}")
    public String getUser(@PathVariable("id") long id){
        return "hello user";
    }
}

当我们执行HTTP请求 GET /user/1的时候,上面的参数id值就被映射为了1。根据上面的源码我们知道,要想解析出参数id,必然有一个参数解析器做了这个工作。

首先打断点到supportsParameter(),查看到底是哪个参数解析器支持这样的处理。通过打断点可以很容易知道是PathVariableMethodArgumentResolver参数解析器支持解析这个参数,那我们就需要问两个问题了:

  1. 为什么PathVariableMethodArgumentResolver可以支持这个参数的解析,它是如何判定的
  2. PathVariableMethodArgumentResolver是如何解析参数,从HTTP请求中将信息抠出赋给id字段的呢?

这两个问题的答案其实也是PathVariableMethodArgumentResolver的两个实现方法:supportsParameter()resolveArgument()

我们先来看第一个方法的源码:

public boolean supportsParameter(MethodParameter parameter) {
    //不带PathVariable注解直接返回false
   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;
}

这个函数的判断逻辑比较简单,首先是判断如果参数上不带@PathVariable注解直接返回false。其次带了注解,判断我们的参数类型是不是Map类型的,如果是的话就获取@PathVariable注解信息并要求其value内容不能为空。最后如果不是Map但是有@PathVariable注解就直接返回true。我们的参数是long型,且标了@PathVariable,因此会直接返回true。

再看第二个源码(其实是PathVariableMethodArgumentResolver的父类AbstractNamedValueMethodArgumentResolver实现的):

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

    //拿到我们的参数名,在上例中,我们的参数名是id(其实就是@PathVariable注解里的value值)
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   MethodParameter nestedParameter = parameter.nestedIfOptional();

   Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
    
    //...
    
    //拿到参数名后,从HTTP请求中解析出这个参数名位置的值,比如 GET /user/1 此时arg就是"1"
   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    //... 一些校验和处理
   if (binderFactory != null) {
       //创建数据绑定器
      WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
      try {
          //使用数据绑定器进行数据转化(如果需要的话)
         arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
      }
    
      //... 一些异常处理
   }

   handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

   return arg;
}

首先我们先看下参数解析器是如何从请求信息中拿到参数值,也即:resolveName(resolvedName.toString(), nestedParameter, webRequest);的实现:

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

可以看到思路很简单,在走到当前步骤之前SpringMVC就做了一个处理,将我们Controller上写的URL与实际请求的URL做了一个映射处理,比如 Controller层为:/user/{id}/{age} ,实际请求为:/user/1/27。这时SpringMVC会根据路径匹配得到K,V,分别是id ->1和age ->27。并将这个Map存储在Request的请求域中,且将它的key值设为HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE。这样我们走到这里的时候再从请求域中根据key值就可以拿到这个信息,信息本身是个Map,再传入resolvedName就可以拿到value,因此很容易通过传入id得到1这个信息。(关心这个Map是何时构建的可以查阅源码的RequestMappingInfoHandlerMapping#extractMatchDetails()函数,它会在RequestMappingInfoHandlerMapping#getHandlerInternal()里被调用,只不过这里面的内容有些多,需要断点打的深一点)

其次我们又看到了数据绑定器,我们前一篇中已经见过它了,当时我们说从HTTP请求中解析出数据后,得能将这些数据绑定到我们的参数上的,这个工作就是数据绑定器干的工作。由于我们的当前例子的参数比较简单(只是一个long id),所以很难发挥数据绑定器的作用,不过我们这里可以先简单的说一下:

我们知道HTTP协议是文本协议,这代表从HTTP请求中解析出的东西都是文本(二进制除外),所谓文本也即字符串,因此上面的resolveName()函数得到的其实是字符串"1",但我们的HandlerMethod参数是long类型,这就需要一个字符串向long类型的转化,而这其实就是数据绑定器做的工作。

如果大家源码打的比较深的话会发现,实际进行转化的核心代码如下:

public class GenericConversionService implements ConfigurableConversionService {
    //...
    public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
        //...
        //拿到转化器进行转化
        GenericConverter converter = getConverter(sourceType, targetType);
        if (converter != null) {
            Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
        }
        //...
    }
    //...

}

而获取转化器的代码如下:

protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
    ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
    GenericConverter converter = this.converterCache.get(key);
    if (converter != null) {
        return (converter != NO_MATCH ? converter : null);
    }

    converter = this.converters.find(sourceType, targetType);
    if (converter == null) {
        converter = getDefaultConverter(sourceType, targetType);
    }

    if (converter != null) {
        this.converterCache.put(key, converter);
        return converter;
    }

    this.converterCache.put(key, NO_MATCH);
    return null;
}

是不是又想到了策略模式?自己持有多个转化器,在需要转化的时候就找到适合的转化器来转化。

在SpringBoot2.7.2版本中,默认情况下的转化器共有124个,部分内容如下:

image-20220906212651245

image-20220906212707262

可以看到这些参数解析器都是将一种数据类型转为另一种数据类型,针对于我们的情况就需要一个将String类型转为Long类型的转化器。

另外需要提一嘴的是,这里的策略模式与之前参数解析器或返回值处理器的设计不同,之前策略接口都都有个类似于support()的功能,主类会挨个问每个策略是否支持解析,支持了再调用它来解析。但是这里是用HashMap来做的。也即我们将情况设计为key值,解决方案设计为value值。这样直接输入key值就能得到策略,无需遍历询问。通过源码也很容易看到:

private final Map<ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap<>(256);

converters是一个Map。其Key是源类型+目标类型

final class ConvertiblePair {

   private final Class<?> sourceType;

   private final Class<?> targetType;
}

另外,SpringMVC支持让我们自定义一些类型转化器的,可以按照自己的规则来做类型转化,我们下一章就会说到。

2. 表单提交

2.1. 源码分析

举例如下:

<form action="/user" method="post">
    <label>
        姓名:
        <input name="name" value="tom"/>
    </label> <br/>
    <label>
        年龄:
        <input name="age" value="18"/>
    </label> <br/>
    <label>
        宠物名字:
        <input name="pet.name" value="myDog"/>
    </label> <br/>
    <label>
        宠物年龄:
        <input name="pet.age" value="18"/>
    </label> <br/>
    <input type="submit" value="提交">
</form>
@RestController
public class FormController {

    @PostMapping("/user")
    public String addUser(User user){
        return user.toString();
    }
}

其中我们的POJO类,User和Pet信息如下:

public class User {
    private String name;
    private int age;
    private Pet pet;
    //省略getter setter
}
public class Pet {
    private String name;
    private int age;
    //省略getter setter
}

当我们点击输入如下信息:

image-20220906214620559

点击提交给后端,我们就能通过前端的参数将这些信息赋值到User对象上

很明显,这是SpringMVC帮我们做的,依据之前的源码经验,我们知道必然有一个参数解析器帮我们做了这个工作,同样通过源码

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

很快可以定位到帮我们解析参数的参数解析器是ServletModelAttributeMethodProcessor,因此这个参数解析器就是处理表单提交的,老规矩我们先看下它为什么能够处理表单请求再看下它是如何解析表单参数的:

public boolean supportsParameter(MethodParameter parameter) {
   return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
         (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}

其中parameter.hasParameterAnnotation(ModelAttribute.class)是指当前参数上包含注解@ModelAttribute,我们这里没有,因此是false。this.annotationNotRequired在当前对象中恒为true(构造方法中构造时就写死的true),最后一个BeanUtils.isSimpleProperty(parameter.getParameterType())是判断当前参数是否是基本类型,我们的参数是User对象,不是基本类型,取反后为true,因此整体返回true。

下面我们再看下ServletModelAttributeMethodProcessor是如何解析参数的:

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

    //...
    //...获得参数名
    String name = ModelFactory.getNameForParameter(parameter);
    //...

    Object attribute = null;
    BindingResult bindingResult = null;
    //...
    //构造参数实例,其实就是调用默认构造方法,得到一个空的对象
    attribute = createAttribute(name, parameter, binderFactory, webRequest);
    //...异常处理

    if (bindingResult == null) {
        //获得数据绑定器
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            if (!mavContainer.isBindingDisabled(name)) {
                //使用数据绑定器,从HTTP请求中取出信息做绑定操作,核心方法
                bindRequestParameters(binder, webRequest);
            }
            //...
        }
        //...
    }
    //将信息加入到ModelAndViewContainer中
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);
    //返回结果
    return attribute;
}

源码中的流程会比较长,这里只截取了主要的部分,其思路比较清晰:

由于我们知道当前参数不是一个简单类型,因此需要先构造出来,通过

attribute = createAttribute(name, parameter, binderFactory, webRequest);

就构造出了一个空的对象(如果点进去源码的话会发现其实就是反射拿到默认构造方法,然后调用默认的构造方法创建对象),比如如果是我们的User对象,执行完这句后就会得到:

我们不妨叫一个壳对象,然后构造数据绑定器,数据绑定器会解析HTTP请求,将HTTP请求的信息绑定到我们的壳对象上,这样我们就得到了一个有意义的对象,其中数据的绑定操作对应源码:

bindRequestParameters(binder, webRequest);

执行完成后,我们的User对象就变成了

因此bindRequestParameters()方法是解析出来参数。

bindRequestParameters()源码如下:

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

servletBinder.bind()源码如下:

public void bind(ServletRequest request) {
    MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
    //... 一些特殊处理
    addBindValues(mpvs, request);
    doBind(mpvs);
}

十分关键的是MutablePropertyValues对象,这里我们已经将form表单提交的参数进行了初步解析,解析为了一个MutablePropertyValues对象,MutablePropertyValues内部有个List,装着解析后的每个参数信息:

这个解析其实不难,我们在提交form表单的时候,HTTP的参数原始数据如下:

name=tom&age=18&pet.name=myDog&pet.age=2,因此我们可以很容易的按&=进行拆分,得到上述MutablePropertyValues信息。

这个信息会非常的关键,我们在后面的数据绑定中就是以这个信息作为信息源来与我们的User对象进行绑定的。

多次源码深入后会走到 DataBinder#applyPropertyValues()方法,其源码如下:

protected void applyPropertyValues(MutablePropertyValues mpvs) {
   try {
      // Bind request parameters onto target object.
      getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
   }
    //...异常处理
}

可以看到就是拿到属性访问器,设置属性。所谓属性访问器,大家不用想的多高大上,其实就是一个对象包装器,通过它可以反射的将一些属性设置值。这里的属性访问器就是BeanWrapperImpl,了解Spring的同学肯定对它很熟悉(其实我们三期网管的协议解析器就用了这个对象,我们可以简单将它理解为一个工具类,这个工具类可以反射设置对象里的属性值)。因此SpringMVC就是通过BeanWrapperImpl将HTTP请求信息绑定到User对象上的。

BeanWrapperImpl#setPropertyValues()函数源码如下:

public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
    throws BeansException {

    List<PropertyAccessException> propertyAccessExceptions = null;
    //获得每个属性的信息,在上面我们已经看到了pvs中是有一个List的,从HTTP请求中解析出的
    List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
                                          ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
    //...
    try {
        //for循环遍历每个属性值,然后设置
        for (PropertyValue pv : propertyValues) {
            try {
                setPropertyValue(pv);
            }
            //... 异常处理
        }
    }
    //...
}

后面一层层的源码会很深,我们可以讲一些比较核心的部分:

在进行setPropertyValue()设置时会走进AbstractNestablePropertyAccessor#processLocalProperty()函数(BeanWrapperImpl继承自AbstractNestablePropertyAccessor):

AbstractNestablePropertyAccessor#processLocalProperty()函数中比较重要的一行信息是:

valueToApply = convertForProperty(
      tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());

其中convertForProperty()内容如下:

protected Object convertForProperty(
      String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td)
      throws TypeMismatchException {

   return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
}

走到这里的时候就可以看到与@PathVariable的一些共同之处,都需要使用类型转化器来转化数据。

这其实也容易理解,HTTP请求提交age=18,解出来的是字符串"18",自然就需要转为int类型。

因此大体的流程为:通过HTTP请求解析form提交的信息,根据KV值解析出来多条,如:

[
    "name": "张三",
    "age": "18",
    "pet.name": "狗子",
    "pet.age": "18"
]

接着调用默认构造方法,new出空壳的参数对象,再借用BeanWrapperImpl,将解析出的多条KV信息绑定到参数对象上。在绑定的过程中可能需要类型转化,比如字符串转整型,这时就需要借助类型转化器来转化数据,将转化后的数据再绑定到属性上。

另外,BeanWrapperImpl其实也是持有124个类型转化器的:

2.2 自定义类型转化器

可以看到,类型转化器会将HTTP请求信息转化为我们参数上对应的数据类型,我们之前也说了SpringBoot2.7.2版本中默认包含124个类型转化器,这些类型转化器大都是基本类型转化器,如String转Integer等。我们可以自定义类型转化器,来扩展Spring自带的转化器功能,举例如下:

<form action="/user" method="post">
    <label>
        姓名:
        <input name="name" value="tom"/>
    </label> <br/>
    <label>
        年龄:
        <input name="age" value="18"/>
    </label> <br/>
    <label>
        宠物信息:
        <input name="pet" value="myDog,2"/>
    </label> <br/>
    <input type="submit" value="提交">
</form>

上例中,我们将pet信息写为**"狗子,18",也即我们想将"狗子,18"**这一信息转为Pet对象,再或者说,我们想将字符串类型转为Pet对象。我们知道SpringBoot默认的数据转化器是没有这种功能的,此时就需要自定义类型转化器:

public class MyPetConverter implements Converter<String, Pet> {
    @Override
    public Pet convert(String source) {
        Pet pet =  new Pet();
        String[] split = source.split(",");
        pet.setName(split[0]);
        pet.setAge(Integer.parseInt(split[1]));
        return pet;
    }
}

自定义类型转化器需要继承自Convert接口,我们这里的转化做的比较粗糙,大家明白就好。

接着我们就需要将自己的自定义转化器注册到SpringBoot中,目前对于SpringMvc功能的增强可以通过自定义一个WebMvcConfigure Bean 或者继承WebMvcConfigure接口实现自己的对象注册到Bean中

@Configuration
public class MyWebMvcConfigure implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new MyPetConverter());
    }
}

这样SpringMVC在进行类型转化的时候,根据form表单提交的信息pet=myDog,2,SpringMVC根据key值pet找到User对象中属性是pet的属性,发现类型是Pet类型,然后就是将myDog,2字符串转为Pet类型,此时转化的源是String,目的是Pet类型,根据这一信息作为key值从converts这个map中获取转化器,很自然的就拿到了我们自己写的转化器,然后调用我们自己写的转化器将字符串转为Pet对象。

2.3 一些补充

我在B站看这块视频的时候看到很多弹幕会提一个问题,我们在2.1节举的例子中,为什么Controller层的方法没加@RequestBody注解?

也即

@RestController
public class FormController {

    @PostMapping("/user")
    public String addUser(User user){
        return user.toString();
    }
}

这段代码中的参数User对象,为什么没有标@RequestBody注解。很多同学会认为只要是Post请求提交的对象,后端都应该是加@RequestBody注解的。

首先@RequestBody注解使用的场景是请求参数在请求体中并且是JSON格式(如果不了解你需要先学习基本的Spring MVC应用知识),而表单提交提交的数据虽然在请求体中,但不是JSON格式的数据,而是param1=value1¶m2=value2格式的数据,因此此处如果加@RequestBody注解会无法进来,从这也可以看出,这两种情况是使用不同的参数解析器来解析的。

注:上面说的不是很贴切,@RequestBody注解虽然拿的是请求体中的数据,但并不一定是JSON,你完全可以这样写:

@PostMapping("/user")
public String postUser(@RequestBody String json){
    return json;
}

这代表直接将请求体中所有的数据拿到作为一个字符串,这时请求体中到底传过来的是不是JSON都无所谓了。

但如果写出

@PostMapping("/user")
public String postUser(@RequestBody User user){
    System.out.println(user);
    return user.toString();
}

则请求体中一定得是JSON格式的。

3. @RequestBody

3.1 源码分析

上面我们已经提了一嘴@RequestBody注解,这个注解主要是将HTTP请求协议体中的JSON数据转为我们的Java对象,举例如下:

@RestController
public class RequestBodyController {

    @PostMapping("/user/2")
    public Object addUse(@RequestBody User user){
        return user;
    }
}

我们使用postman模拟发送请求如下:

很明显又是SpringMVC将HTTP请求体中的数据取出来,转为了我们的User对象并设置赋给了我们的参数,那么就来看看是哪个参数解析器做的工作:

打断点定位是哪个参数解析器的工作我们不再重复了,实际上是RequestResponseBodyMethodProcessor参数解析器,我们还是看两个内容,为什么它支持解析这种情况,以及它是如何解析的:

supportsParameter()源码如下:

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return parameter.hasParameterAnnotation(RequestBody.class);
}

可以看到很简单,就是判断参数上是否有@RequestBody注解,凡是有这个注解的就支持,我们的User对象上有这个注解,所以很明显,当前参数解析器能解析这种情况。

下面我们就看下RequestResponseBodyMethodProcessor是如何解析参数的:

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

    parameter = parameter.nestedIfOptional();
    //核心的参数解析
    Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
    //...

    return adaptArgumentIfNecessary(arg, parameter);
}

readWithMessageConverters()函数会将HTTP请求体中的JSON转为我们的Java对象,其源码如下:

@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
                                               Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    //拿到请求类型
    MediaType contentType;
    boolean noContentType = false;
    try {
        contentType = inputMessage.getHeaders().getContentType();
    }
    //...
    
    //拿到我们的参数类型和Controller类型
    Class<?> contextClass = parameter.getContainingClass();
    Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
    //...

    //拿到HTTP请求类型
    HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
    Object body = NO_VALUE;

    EmptyBodyCheckingHttpInputMessage message = null;
    try {
        message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
        
        //开始遍历所有的HttpMessageConverter,看看谁支持将当前http请求内容转为我们的参数
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConve