从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就能获取泛型信息呢?
从上图可以看出,这里返回了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