从Spring中List请求数据绑定到Type在反序列化中的应用

一、List请求数据绑定

1.1 一个问题

对于如下接口展开研究:Http请求中一些复杂的参数是怎么绑定到要访问的接口上的呢?

@RequestMapping("/demo")
@RestController
public class HelloWorld {

    @PostMapping("/list")
    public String hello(@RequestBody List<User> userList){
        return userList.get(0).getName();
    }

    @PostMapping("/array")
    public String hello1(@RequestBody User[] userList){
        return userList[0].getName();
    }

}
复制代码

在sofaboot中访问接口/demo/list,发现参数绑定不上,改成/demo/array就可以, 然而在spring中访问/demo/list可以成功。

sofaboot访问时的错误栈:

{
    "data": null,
    "errorCode": "UNKOWN",
    "errorMsg": "com.alibaba.fastjson.JSONObject cannot be cast to com.alipay.fppolicy.findx.model.config.abnormal.result.ExceptionTask",
    "stackTrace": "
    java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.alipay.fppolicy.findx.model.config.abnormal.result.ExceptionTask
}
复制代码

从报错中可以看出发生了类型转换异常,并且Sofaboot将默认的Jackson的解析替换为了Fastjson(当然这个不影响今天讨论问题)。

然而本地新建一个标准的Springboot工程,相同的参数是可以绑定成功并进一步调用方法的。因此判定一定是sofaboot实现中存在bug。

猜测:sofaboot在解析List泛型参数时,由于类型擦除的原因只知道是个List,却不知道泛型的具体类型,导致无法绑定。对于Array,其类型信息是是确定的,因此可以解析成功。

1.2 验证猜测

按照调用链,找到了sofaboot中负责解析方法入参类型的关键代码

CLass clazz = methodParameter.getParameterType()
复制代码

然后在实现的默认JsonHttpMessageConverter中使用该clazz进行解析

Object value = parse.parseObject(clazz);
复制代码

这样解析出来的效果类似下面这段代码

public static void main(String[] args) {
    List<User> list = new ArrayList<User>();
    User zhangSan = new User();
    zhangSan.setAge(1);
    zhangSan.setName("z3");
    list.add(zhangSan);

    String json = JSON.toJSONString(list);
    System.out.println(json);
    
    List list1 = JSON.parseObject(json, List.class);
    System.out.println(list1);
    System.out.println(list1.get(0).getClass());

    User[] array = JSON.parseObject(json, User[].class);
    System.out.println(JSON.toJSONString(array));
}

console:
[{"age":1,"name":"z3"}]
[{"name":"z3","age":1}]
class com.alibaba.fastjson.JSONObject
[{"age":1,"name":"z3"}]
复制代码

可以看出,json被解析为了List<JSONObject>,这样就跟报错对应上了。

1.3 spring为什么可以解析成功

接下来的问题是,springboot是怎么知道具体类型的呢?

终于可以大胆的贴源码了...删去了不重要的部分

org.springframework.web.method.support.InvocableHandlerMethod

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

   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

   return doInvoke(args);
}

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

   MethodParameter[] parameters = getMethodParameters();

   Object[] args = new Object[parameters.length];
   for (int i = 0; i < parameters.length; i++) {
      MethodParameter parameter = parameters[i];

      try {
         args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
      }
      catch (Exception ex) {
         throw ex;
      }
   }
   return args;
}

复制代码

处理调用请求中,最终委托InvocableHandlerMethod来解析参数,得到args后再执行调用; 而解析参数由特定的resolvers处理。

继续跟代码

org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

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

   //由于参数被@RequestBody注释,所以这里会返回org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
   HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
   if (resolver == null) {
      throw new IllegalArgumentException();
   }
   return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
复制代码
org.springframework.web.servlet.mvc.method.annotation.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());
   String name = Conventions.getVariableNameForParameter(parameter);

   if (binderFactory != null) {}

   return adaptArgumentIfNecessary(arg, parameter);
}


@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

   HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
   Assert.state(servletRequest != null, "No HttpServletRequest");
   ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
   
   Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
   if (arg == null && checkRequired(parameter)) {
      throw new HttpMessageNotReadableException();
   }
   return arg;
}

复制代码

到这里关键性代码出现了 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());

这里不是根据methodParameter.getParameterType()返回的Class信息去解析,而是用getNestedGenericParameterType()返回的一个Type,这个接口还是第一次见,然后后面才知道早就用过了。

java.lang.reflect.Type

public interface Type {
    /**
     * Returns a string describing this type, including information
     * about any type parameters.
     *
     */
    default String getTypeName() {
        return toString();
    }
}
复制代码

这是反射包里的一个接口。那么实际执行时,返回了哪个实体类呢?为什么用Type就能获取泛型信息呢?

image.png

从上图可以看出,这里返回了ParameterizedTypeImpl类,其中的rawType与actualTypeArguments一起标识出泛型集合的具体类型。

package sun.reflect.generics.reflectiveObjects;

public class ParameterizedTypeImpl implements ParameterizedType {
    private final Type[] actualTypeArguments;
    private final Class<?> rawType;
    private final Type ownerType;
    
    ...
}
复制代码

到这里就搞清楚sofaboot和spring实现上的区别了。

1.4 结论

sofaboot直接使用了Class来进行反序列化。 spring中使用了Type来获取比Class中更全的类型信息。

二、java.lang.reflect.Type在Jackson反序列化中的应用

接下来研究一下是怎么通过ParameterizedType完成数据的反序列化的。

ObjectMapper objectMapper = new ObjectMapper();
List<User> list2 = objectMapper.readerFor(new TypeReference<List<User>>() {}).readValue(json);
复制代码

如上是平时开发中使用Jackson反序列化泛型集合的常用,这里的TypeReference又是怎么工作的?

com.fasterxml.jackson.databind.ObjectMapper

public ObjectReader readerFor(TypeReference<?> type) {
    return _newReader(getDeserializationConfig(), _typeFactory.constructType(type), null,
            null, _injectableValues);
}
复制代码
com.fasterxml.jackson.databind.type.TypeFactory

public JavaType constructType(TypeReference<?> typeRef)
{
    return _fromAny(null, typeRef.getType(), EMPTY_BINDINGS);
}
复制代码

public abstract class TypeReference<T> implements Comparable<TypeReference<T>> {
    protected final Type _type;

    protected TypeReference() {
        Type superClass = this.getClass().getGenericSuperclass();
        if (superClass instanceof Class) {
            throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
        } else {
            this._type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
        }
    }

    public Type getType() {
        return this._type;
    }

    public int compareTo(TypeReference<T> o) {
        return 0;
    }
}
复制代码

最后仍然是通过ParameterizedType完成的。

Supongo que te gusta

Origin juejin.im/post/7066382678827728904
Recomendado
Clasificación