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