将JSONObject转换为对象时,发现转换后的对象为空,代码如下
@Getter
@Setter
@Entity
@Table(name = "image")
@Accessors(chain = true)
public class MqHouseImage {
@Id
@Column(name = "id")
private String image_id;
//图片地址
private String url = null;
}
@Test
public void t(){
String image = "{\"image_id\":\"22\",\"url\":\"eagle/3257/house/2020/05/22/16/6fe357ae42d155042aeb4157c6a31a5e383501ae266.jpg\"}";
JSONObject jsonObject = JSONObject.fromObject(image);
MqHouseImage mqHouseImage = (MqHouseImage) JSONObject.toBean(jsonObject, MqHouseImage.class);
System.out.println(mqHouseImage);
}
运行结果:
2020-05-28 17:54:04.847 INFO 6788 --- [ main] net.sf.json.JSONObject : Property 'image_id' of class com.web.website.entity.toqj.HouseImage has no write method. SKIPPED.
2020-05-28 17:54:04.847 INFO 6788 --- [ main] net.sf.json.JSONObject : Property 'url' of class com.web.website.entity.toqj.HouseImage has no write method. SKIPPED.
com.web.website.entity.toqj.HouseImage@74889ebe
debug
发现BeanUtils底层使用了PropertyDescriptor反射获取属性,通过PropertyDescriptor的getWriteMethod()方法获取属性的setter方法,最后通过setter方法给属性赋值, getWriteMethod()方法返回了 null(也就是获取不到setter方法),导致后续没有执行赋值
getWriteMethod()源码
public synchronized Method getWriteMethod() {
Method writeMethod = this.writeMethodRef.get();
if (writeMethod == null) {
Class<?> cls = getClass0();
if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) {
// The write method was explicitly set to null.
return null;
}
// We need the type to fetch the correct method.
Class<?> type = getPropertyType0();
if (type == null) {
try {
// Can't use getPropertyType since it will lead to recursive loop.
type = findPropertyType(getReadMethod(), null);
setPropertyType(type);
} catch (IntrospectionException ex) {
// Without the correct property type we can't be guaranteed
// to find the correct method.
return null;
}
}
if (writeMethodName == null) {
writeMethodName = Introspector.SET_PREFIX + getBaseName();
}
Class<?>[] args = (type == null) ? null : new Class<?>[] { type };
writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
if (writeMethod != null) {
if (!writeMethod.getReturnType().equals(void.class)) {
writeMethod = null;
}
}
try {
setWriteMethod(writeMethod);
} catch (IntrospectionException ex) {
// fall through
}
}
return writeMethod;
}
通过分析getWriteMethod()源码,发现setter方法的返回值必须是void,否则返回null
if (writeMethod != null) {
if (!writeMethod.getReturnType().equals(void.class)) {
writeMethod = null;
}
}
使用refactor 菜单的 delombok 选项将lombok 注解转换成源码发现,@Accessors(chain = true)生成的setter方法返回的不是void而是对象本身,源码如下
@Entity
@Table(name = "image")
@Accessors(chain = true)
public class HouseImage {
@Id
@Column(name = "id")
private String image_id;
//图片地址
private String url = null;
public String getImage_id() {
return this.image_id;
}
public String getUrl() {
return this.url;
}
public HouseImage setImage_id(String image_id) {
this.image_id = image_id;
return this;
}
public HouseImage setUrl(String url) {
this.url = url;
return this;
}
}
结:@Accessors(chain = true) 注解导致JavaBean生成的 setter 方法不被 PropertyDescriptor 识别,导致获取 setter为null
进一步验证@Builder,发现需添加@NoArgsConstructor @AllArgsConstructor,否则会报如下异常
net.sf.json.JSONException: java.lang.NoSuchMethodException: com.web.website.entity.toqj.HouseImage.<init>()
at net.sf.json.JSONObject.toBean(JSONObject.java:288)
at net.sf.json.JSONObject.toBean(JSONObject.java:233)
at com.web.website.unit.image.MapInfoUnitTest.t(MapInfoUnitTest.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.lang.NoSuchMethodException: com.web.website.entity.toqj.HouseImage.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at net.sf.json.util.NewBeanInstanceStrategy$DefaultNewBeanInstanceStrategy.newInstance(NewBeanInstanceStrategy.java:55)
at net.sf.json.JSONObject.toBean(JSONObject.java:282)
... 32 more
使用@Builder源码
public class HouseImage {
@Id
@Column(name = "id")
private String image_id;
//图片地址
private String url = null;
HouseImage(String image_id, String url) {
this.image_id = image_id;
this.url = url;
}
public static HouseImageBuilder builder() {
return new HouseImageBuilder();
}
public String getImage_id() {
return this.image_id;
}
public String getUrl() {
return this.url;
}
public void setImage_id(String image_id) {
this.image_id = image_id;
}
public void setUrl(String url) {
this.url = url;
}
public static class HouseImageBuilder {
private String image_id;
private String url;
HouseImageBuilder() {
}
public HouseImage.HouseImageBuilder image_id(String image_id) {
this.image_id = image_id;
return this;
}
public HouseImage.HouseImageBuilder url(String url) {
this.url = url;
return this;
}
public HouseImage build() {
return new HouseImage(image_id, url);
}
public String toString() {
return "HouseImage.HouseImageBuilder(image_id=" + this.image_id + ", url=" + this.url + ")";
}
}
}