阅读 95

从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完成的。


作者:黯淡之星
链接:https://juejin.cn/post/7066382678827728904


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐