大家好,我是烤鸭:
lombok 导致 springmvc 使用 @RequestBody注解 接收 json数据 对象参数绑定失败。
环境版本:
spring 5.x
1. 场景复现
问题出现在创建对象的属性名称。比如我有一个类中的属性值是
String aTest;
首字母小写,第二个字母大写。
lombok 生成的get/set 方法是 getATest/setATest。
而依据java的规范(用各种ide生成的get/set方法) 应该是 getaTest/setaTest。
2. 问题猜测
lombok 生成的方法不符合 JavaBean get/set 规范,导致mvc 无法封装进入对象。
尝试 form表单提交的方式 是没有问题的。
问题在于json 的方式。或者说在于json序列化和反序列化时。
spring json的包 默认使用的 com.fasterxml.jackson
可以看一下 com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap
的 _findWithAlias 方法,如图:
BeanPropertyMap 获取到类中的属性是 atest,而前台传入的key是 aTest,无法匹配。
将 前台传参改为
{"atest":"1","age":"2"}
就可以匹配上了。
3.解决
不使用lombok 或者 重写 get/set方法。
或者 不使用 com.fasterxml.jackson 序列化,使用 fastjson。
看下有人提出过issue,而作者也提出了解决办法。
https://github.com/rzwitserloot/lombok/issues/1661
4.深入源码看一下
首先分析一下 com.fasterxml.jackson。
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {// ... 省略// 关键点部分 1 body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :((HttpMessageConverter<T>) converter).read(targtestlass, msgToUse));// ... 省略
}
后边调用的就是 BeanPropertyMap 的find 方法。
再看一下 com.alibaba.fastjson。
通过修改 默认 json解析器。原来 AbstractJackson2HttpMessageConverter 改为 FastJsonHttpMessageConverter
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer
的 deserialze 方法。
可以看到 fastjson 将原来的属性 放到 fieldinfo 中。后续操作的的都是fieldinfo 对象。
5. 测试
前面写的还不够清晰,我们可以写个简单的test方法自己测一下。
ATest.java
package com.test.test.test;import lombok.Data;@Data
public class ATest{String aTest;String age;}
TestJson.java
package com.test.test;import com.alibaba.fastjson.JSONObject;
import com.test.controller.test.ATest;
import org.junit.Test;import java.util.HashMap;public class TestJson{@Testpublic void testJson() {HashMap<Object, Object> hashMap = new HashMap<>();hashMap.put("aTest","111");hashMap.put("age","111");ATest test = JSONObject.parseObject(JSONObject.toJSONString(hashMap), ATest.class);System.out.println("fastjson=================="+test);ATest o = (ATest) net.sf.json.JSONObject.toBean(net.sf.json.JSONObject.fromObject(hashMap), ATest.class);System.out.println("net.sf.json=================="+o);}
}
可以发现,fastjson 反序列化话的对象是正常的。
net.sf.json 是不行的。
net.sf.json 使用的是 org.apache.commons.beanutils.PropertyUtilsBean
getPropertyDescriptors
beanInfo.getPropertyDescriptors();
可以看到获取到的 beanInfo 的 propertyName 是 ATest。
事实证明, lombok 这波操作确实有点问题,起码不兼容市面上大部分的json包。
总结:
最开始以为是mvc 无法封装 的问题,后来发现跟lombok有关(首字母小写、第二个字母大写时的get/set方法生成方式)。
form表单提交没问题,发现跟json有关(序列化/反序列化)。
再换了几个常见的json包。
fastjson 没问题,net.sf.json 和 jackson 都不行。
lombok 这波操作就是逆规范的,没什么好说的。如果非要这种命名的话,建议重写get/set 方法。