com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct (no Creators)

摘要

记录下使用lombok遇到的反序列问题,一开始在lombok1.16.18中并没有发现,然后应用中没有指定lombok全局版本,引入的其他二方包将lombok版本提升到了1.16.20,然后报错。因为这个问题需要允许时才能发现,很可能会造成线上故障,所以不能等到出现问题时才发现,需要提前知晓。

错误栈

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.fs.jackson.Address` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{"id":1,"address":"address"}"; line: 1, column: 2]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1297)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
	at Application.testSeriable(Application.java:36)
	at Application.run(Application.java:24)
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:790)
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234)
	at Application.main(Application.java:27)

问题

问题1:

手贱把一个private修饰符写成了final

@Data
public class AddressVO {
     final Long id;
    private String address;
}

问题2 使用了Bulider类

@Data
@Accessors(chain = true)
@Builder
public class Address {
    private Long id;
    private String address;
}

分析

分析lombok导致的问题很简单,查看下编译后的class文件即可。
使用jd-gui反编译工具查看一下

1.6.18

public class AddressByBuilder {
    private Long id;
    private String address;

    public static class AddressBuilder {
        private Long id;
        private String address;
        // 省略
    }

    @ConstructorProperties({ "id", "address" })
    AddressByBuilder(Long id, String address) {
        this.id = id;
        this.address = address;
    }
}

1.16.20

public class AddressByLombok20 {

    private Long id;
    private String address;
	// 构造函数
    AddressByLombok20(Long id, String address) {
        this.id = id;
        this.address = address;
    }
}

对比1.16.28与1.16.20发现构造函数发生了变化。1.16.20中构造函数少了@ConstructorProperties({ "id", "address" })
JDK1.6中出来的

The annotation shows that the first parameter of the constructor can be retrieved with the getX() method and the second with the getY() method. Since parameter names are not in general available at runtime, without the annotation there would be no way to know whether the parameters correspond to getX() and getY() or the other way around.

@ConstructorProperties()注解用于构造函数上,表示构造函数可以通过GetName来找到,第一个参数可以使用getX()方法检索,第二个参数可以使用方法检索getY()。由于方法参数名一般在运行时不可见,如果没有标注就没有办法知道参数是否符合getX() 和getY()或周围的其他方法。

这显然是lombok升级过程中的一个不兼容的改造。

因为我们都没有定义无参构造函数,所以会找已有的构造函数,然后匹配getter/setter函数。

解决

1.maven中指定lombok固定版本,使用1.16.18版本,代码层面不需要做更改
2.类中添加无参构造器

建议

需要序列化的类,比如与前端交互,rpc调用,都加上无参构造器,兼容性比较好

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct (no Creators)_第1张图片

参考

http://tutorials.jenkov.com/java-json/jackson-objectmapper.html#how-jackson-objectmapper-matches-json-fields-to-java-fields、
https://www.jianshu.com/p/f826ba2dbcf9

你可能感兴趣的:(java,java,web,知识汇总)