lombok反序列化报错原因定位

Posted by LiuShuo on April 25, 2018

Background

线上使用lombok-1.16.20版本的依赖生成相关model对象的getter/setter/constructor/builder相关方法。但是在升级了1.16.20版本后线上Jackson反序列化报错:

“no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)”)

Problem

@Builder的使用问题

如果使用@Builder注解,在1.16.20版本下会生成包访问权限的全参构造函数,没有无参数构造函数(用1.16.18也不会生成无参数构造函数)

1
2
3
4
5
6
7
  ResourceConfigRule(int type, String url, String scale, String webpUrl, String webpScaleUrl, boolean needVerify)

  {

    this.type = type;this.url = url;this.scale = scale;this.webpUrl = webpUrl;this.webpScaleUrl = webpScaleUrl;this.needVerify = needVerify;

  }

这种生成的字节码会在jackson反序列化的时候报错找不到合适的构造函数。

临时策略

添加@AllArgsConstructor和@NoArgsConstructor,在生成的字节码中看到此时会将对应的构造函数声明为public和public 访问权限的生成无参数构造函数,即@Builder的构造函数策略被覆盖,此时就不会报错可以正常反序列化json为对象

1
2
3
4
5
6
7
8
9
public ResourceConfigRule(int type, String url, String scale, String webpUrl, String webpScaleUrl, boolean needVerify)

  {

    this.type = type;this.url = url;this.scale = scale;this.webpUrl = webpUrl;this.webpScaleUrl = webpScaleUrl;this.needVerify = needVerify;

  }

public ResourceConfigRule() {}

原因

但是这并不是1.16.20报错的原因,即不添加@AllArgsConstructor和@NoArgsConstructor在1.16.18版本中也不会报错,而只使用了@Data、@Builder。

反编译一下代码发现两个版本生成的字节码是不同的,18版本的全参构造函数有一个注解@ConstructorProperties

1
2
3
4
5
6
7
8
9
@ConstructorProperties({"type", "url", "scale", "webpUrl", "webpScaleUrl", "needVerify"})

  ResourceConfigRule(int type, String url, String scale, String webpUrl, String webpScaleUrl, boolean needVerify)

  {

    this.type = type;this.url = url;this.scale = scale;this.webpUrl = webpUrl;this.webpScaleUrl = webpScaleUrl;this.needVerify = needVerify;

  }

这个注解在1.16.18的changelog中有提到:

  • CHANGE: @ConstructorProperties will now also be generated for private and package private constructors. This is useful for Jackson Issue #1180

也就说这个注解可以解决Jackson反序列化的问题的。

而1.16.20版本的changelog里有这么一条BREAKING CHANGE:

  • BREAKING CHANGE: lombok config key lombok.anyConstructor.suppressConstructorProperties is now deprecated and defaults to true, that is, by default lombok no longer automatically generates @ConstructorProperties annotations. New config key lombok.anyConstructor.addConstructorProperties now exists; set it to true if you want the old behavior. Oracle more or less broke this annotation with the release of JDK9, necessitating this breaking change.

很明显最新的1.16.20版本已经将该注解suppress掉不再生成,但是保留了一个配置。这也是报错的根本原因,Jackson反序列化一个bean 的时候是采用了这种策略的,具体参考BeanDeserializerBase类的resolve()方法和

deserializeFromObjectUsingNonDefault()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected Object deserializeFromObjectUsingNonDefault(JsonParser p,
 DeserializationContext ctxt) throws IOException
    {
        final JsonDeserializer<Object> delegateDeser = _delegateDeserializer();
 if (delegateDeser != null) {
            return _valueInstantiator.createUsingDelegate(ctxt,
 delegateDeser.deserialize(p, ctxt));
 }
        if (_propertyBasedCreator != null) {
            return _deserializeUsingPropertyBased(p, ctxt);
 }
        // should only occur for abstract types...
 if (_beanType.isAbstract()) {
            return ctxt.handleMissingInstantiator(handledType(), p,
 "abstract type (need to add/enable type information?)");
 }
        // 25-Jan-2017, tatu: We do not actually support use of Creators for non-static
 // inner classes -- with one and only one exception; that of default constructor!
 // -- so let's indicate it
 Class<?> raw = _beanType.getRawClass();
 if (ClassUtil.isNonStaticInnerClass(raw)) {
            return ctxt.handleMissingInstantiator(raw, p,
"can only instantiate non-static inner class by using default, no-argument constructor");
 }
        return ctxt.handleMissingInstantiator(raw, p,
"no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)");
 }

How to Fix

  • 1.使用1.16.18版本,不需要修改老代码,最简单

  • 2.使用最新版本,需要在project根路径下添加文件lombok.config,并且设置

lombok.anyConstructor.addConstructorProperties = true

  • 3.如果使用了@Builder则添加@AllArgsConstructor和@NoArgsConstructor

  • 4.如果没有使用@Builder,用@Data不会有这个问题,因为它会生成无参数public的构造函数

Reference

  • https://docs.oracle.com/javase/7/docs/api/java/beans/ConstructorProperties.html
  • https://github.com/rzwitserloot/lombok/issues/1180
  • https://projectlombok.org/features/configuration
  • https://projectlombok.org/changelog

本文首次发布于 LiuShuo’s Blog, 转载请保留原文链接.