Jackson 和 Lombok:当属性是对象时出现 MismatchedInputException
Jackson and Lombok: MismatchedInputException when Attributes are Objects
我有一份工作 class 看起来像这样:
@Entity
@Table
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Data
@NoArgsConstructor
public class Job implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column(nullable = false)
private String job;
@ManyToOne
@JsonIgnoreProperties("jobs")
private Job next;
以及创造就业机会的方法:
@PostMapping("/jobs")
public ResponseEntity<Job> createJob(@Valid @RequestBody Job job) throws URISyntaxException {
Job result = jobRepository.save(job);
return ...
}
到这里为止一切正常。将 @AllArgsConstructor 添加到作业 class 现在会导致 MismatchedInputException。
Bad Request: JSON parse error: Cannot construct instance of `xxx.domain.Job` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `xxx.domain.Job` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
我尝试将 lombok.anyConstructor.addConstructorProperties = true
添加到 lombok.config 文件,如我找到的可能答案中所述。但这只会导致以下错误:
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ project-name ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 27 source files to /path/target/test-classes
Unknown key 'lombok.anyConstructor.addConstructorProperties' (/path/lombok.config:2)
我尝试使用旧版本的 lombok,因为我在更新 lombok 时发现了一些 github 解决此问题的问题。另外,没有成功(试过 1.16.18 版)
我还尝试删除 private Job next
属性,这使得代码可以正常工作。所以这一定是个问题?
总结
您可能不想要任何形式的 @ConstructorProperties
。
由于不幸的原因,密钥 lombok.anyConstructor.addConstructorProperties = true
不再可用。正确的密钥是 lombok.anyConstructor.suppressConstructorProperties = false
,尽管您可能不想实际应用此配置密钥。
解决方法可能是更新您的构建工具以告知 javac 在 class 文件中生成参数名称信息。
你不需要@ConstructorProperties
@ConstructorProperties
注解的'point'有两个作用:允许运行时间码(java代码,使用反射API)检查names 构造函数的参数,并允许编译时工具找出构造函数参数的 names,如果所有可用的是编译的 (class) 文件,而不是源 (java) 文件。
然而,就此而言,@ConstructorProperties
已过时。如今,大多数编译器配置已更新为默认在 class 文件中包含参数名称,并且反射 API 已更新,以便您轻松获取这些参数。例如:
public class Example {
public Example(String a, int b) {}
public static void main(String[] args) throws Exception {
System.out.println(Example.class
.getConstructor(String.class, int.class)
.getParameters()[1]
.getName());
}
}
然后在命令行上:
javac Example.java
java Example
arg1
javac -parameters Example.java
java Example
b
您可能希望在您的构建中包含上述源文件作为快速检查,以确保 -parameters
选项由您用于构建源文件的任何构建系统设置。它应该打印 b
,如果不打印(如果打印 arg1
),请快速搜索如何在编译期间制作 maven 或 gradle 或任何相关的发出参数名称信息.
现在杰克逊有机会使用这些信息(而不是依赖 @ConstructorProperties
)。我实际上不知道它是否确实如此,但我会这样认为:这是 'new' 的方法。
为什么你不想要它
...因为 java 社区不能信任它,历史证明了这一点。
注解开始是一个简单、清晰的设计(你用它来传递注解形式的参数名称),几乎立即,整个社区都搞砸了:android 和 GWT如果此注释甚至在您的 java 文件中,将拒绝编译您的代码。
然后,有一段时间,一切都很好,所有可以在以 .java
扩展名结尾的文件中编译代码的所有最新版本都至少会默默地忽略此注释。
但是后来 java9 模块系统出来了,在 oracle 的无限智慧中,他们决定......将这个注解推入...... java.desktop
模块???直到今天我完全不知道那里发生了什么,因为那似乎是一个 wildly 不合适的模块。但是,这确实意味着如果您想在模块化构建中以任何形式使用 ConstructorProperties
(即根目录中带有 module-info.java
文件的任何内容),您需要向其添加 requires java.desktop;
,除了这往往意味着没有意义的事情(例如:你的模块不能 运行 在无头 JVM 上)。
这涉及到两点:[1] 为什么 constructorproperties 的 lombok 配置和行为如此混乱,以及 [2] 为什么我建议您不要使用 @ConstructorProperties
.
Lombok 的配置
如果不管上面的建议你真的想使用ConstructorProperties
,在lombok中正确的配置是:
lombok.anyConstructor.suppressConstructorProperties = false
由于上述历史,用于生成 @ConstructorProperties
的 lombok 中的配置键已经经历了一些不幸的疯狂旅程。对于这给您造成的困难,我深表歉意。 (我是 lombok 的核心贡献者)。
最初,addConstructorProperties
存在(相对于简单得多:始终生成此注释,并且根本没有配置密钥)因为 android 和 GWT 无法处理它的存在,并且在我们估计,使用 android/GWT+lombok 的人多于依赖 @ConstructorProperties
生成的人,因此我们决定适当的默认行为应该是在生成构造函数时不包含注释。
然后当 GWT/Android 解决了这个问题时,权力的平衡发生了变化:我们现在意识到 'generate it by default' 更有可能帮助而不是伤害。我们真的非常希望我们的属性始终 'default to false',因此,我们将密钥重命名为 lombok.anyConstructor.suppressConstructorProperties
。
然后在更高版本的 lombok 中,在放弃 'we wish all things to default to false' 格言之后,我们将默认行为改回原来的样子:Do NOT generate ConstructorProperties
默认情况下。如果您使用模块化构建,这样做会导致代码根本无法编译,这是正确的,因为注释被放置在 java.desktop
模块中。虽然整个 java 社区继续在很大程度上避免拼图,但仍有一些人使用它,这足以改变权力平衡。此外,我们现在很清楚 @ConstructorProperties
作为一种机制注定要失败,使用 -parameters
切换正确的,更少头痛的解决方案。
我们预计 lombok 的行为不会第三次改变。
我发现了这个 github 问题:https://github.com/meltmedia/jackson-crypto/issues/6
如那里所述,我通过设置解决了问题:
jackson:
mapper:
INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES: false
Jackson Doc 说:
Feature is mostly used to help interoperability with frameworks like Lombok that may automatically
generate ConstructorProperties annotation but without necessarily
meaning that constructor should be used as Creator for
deserialization.
我有一份工作 class 看起来像这样:
@Entity
@Table
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Data
@NoArgsConstructor
public class Job implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column(nullable = false)
private String job;
@ManyToOne
@JsonIgnoreProperties("jobs")
private Job next;
以及创造就业机会的方法:
@PostMapping("/jobs")
public ResponseEntity<Job> createJob(@Valid @RequestBody Job job) throws URISyntaxException {
Job result = jobRepository.save(job);
return ...
}
到这里为止一切正常。将 @AllArgsConstructor 添加到作业 class 现在会导致 MismatchedInputException。
Bad Request: JSON parse error: Cannot construct instance of `xxx.domain.Job` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `xxx.domain.Job` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
我尝试将 lombok.anyConstructor.addConstructorProperties = true
添加到 lombok.config 文件,如我找到的可能答案中所述。但这只会导致以下错误:
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ project-name ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 27 source files to /path/target/test-classes
Unknown key 'lombok.anyConstructor.addConstructorProperties' (/path/lombok.config:2)
我尝试使用旧版本的 lombok,因为我在更新 lombok 时发现了一些 github 解决此问题的问题。另外,没有成功(试过 1.16.18 版)
我还尝试删除 private Job next
属性,这使得代码可以正常工作。所以这一定是个问题?
总结
您可能不想要任何形式的 @ConstructorProperties
。
由于不幸的原因,密钥 lombok.anyConstructor.addConstructorProperties = true
不再可用。正确的密钥是 lombok.anyConstructor.suppressConstructorProperties = false
,尽管您可能不想实际应用此配置密钥。
解决方法可能是更新您的构建工具以告知 javac 在 class 文件中生成参数名称信息。
你不需要@ConstructorProperties
@ConstructorProperties
注解的'point'有两个作用:允许运行时间码(java代码,使用反射API)检查names 构造函数的参数,并允许编译时工具找出构造函数参数的 names,如果所有可用的是编译的 (class) 文件,而不是源 (java) 文件。
然而,就此而言,@ConstructorProperties
已过时。如今,大多数编译器配置已更新为默认在 class 文件中包含参数名称,并且反射 API 已更新,以便您轻松获取这些参数。例如:
public class Example {
public Example(String a, int b) {}
public static void main(String[] args) throws Exception {
System.out.println(Example.class
.getConstructor(String.class, int.class)
.getParameters()[1]
.getName());
}
}
然后在命令行上:
javac Example.java
java Example
arg1
javac -parameters Example.java
java Example
b
您可能希望在您的构建中包含上述源文件作为快速检查,以确保 -parameters
选项由您用于构建源文件的任何构建系统设置。它应该打印 b
,如果不打印(如果打印 arg1
),请快速搜索如何在编译期间制作 maven 或 gradle 或任何相关的发出参数名称信息.
现在杰克逊有机会使用这些信息(而不是依赖 @ConstructorProperties
)。我实际上不知道它是否确实如此,但我会这样认为:这是 'new' 的方法。
为什么你不想要它
...因为 java 社区不能信任它,历史证明了这一点。
注解开始是一个简单、清晰的设计(你用它来传递注解形式的参数名称),几乎立即,整个社区都搞砸了:android 和 GWT如果此注释甚至在您的 java 文件中,将拒绝编译您的代码。
然后,有一段时间,一切都很好,所有可以在以 .java
扩展名结尾的文件中编译代码的所有最新版本都至少会默默地忽略此注释。
但是后来 java9 模块系统出来了,在 oracle 的无限智慧中,他们决定......将这个注解推入...... java.desktop
模块???直到今天我完全不知道那里发生了什么,因为那似乎是一个 wildly 不合适的模块。但是,这确实意味着如果您想在模块化构建中以任何形式使用 ConstructorProperties
(即根目录中带有 module-info.java
文件的任何内容),您需要向其添加 requires java.desktop;
,除了这往往意味着没有意义的事情(例如:你的模块不能 运行 在无头 JVM 上)。
这涉及到两点:[1] 为什么 constructorproperties 的 lombok 配置和行为如此混乱,以及 [2] 为什么我建议您不要使用 @ConstructorProperties
.
Lombok 的配置
如果不管上面的建议你真的想使用ConstructorProperties
,在lombok中正确的配置是:
lombok.anyConstructor.suppressConstructorProperties = false
由于上述历史,用于生成 @ConstructorProperties
的 lombok 中的配置键已经经历了一些不幸的疯狂旅程。对于这给您造成的困难,我深表歉意。 (我是 lombok 的核心贡献者)。
最初,addConstructorProperties
存在(相对于简单得多:始终生成此注释,并且根本没有配置密钥)因为 android 和 GWT 无法处理它的存在,并且在我们估计,使用 android/GWT+lombok 的人多于依赖 @ConstructorProperties
生成的人,因此我们决定适当的默认行为应该是在生成构造函数时不包含注释。
然后当 GWT/Android 解决了这个问题时,权力的平衡发生了变化:我们现在意识到 'generate it by default' 更有可能帮助而不是伤害。我们真的非常希望我们的属性始终 'default to false',因此,我们将密钥重命名为 lombok.anyConstructor.suppressConstructorProperties
。
然后在更高版本的 lombok 中,在放弃 'we wish all things to default to false' 格言之后,我们将默认行为改回原来的样子:Do NOT generate ConstructorProperties
默认情况下。如果您使用模块化构建,这样做会导致代码根本无法编译,这是正确的,因为注释被放置在 java.desktop
模块中。虽然整个 java 社区继续在很大程度上避免拼图,但仍有一些人使用它,这足以改变权力平衡。此外,我们现在很清楚 @ConstructorProperties
作为一种机制注定要失败,使用 -parameters
切换正确的,更少头痛的解决方案。
我们预计 lombok 的行为不会第三次改变。
我发现了这个 github 问题:https://github.com/meltmedia/jackson-crypto/issues/6
如那里所述,我通过设置解决了问题:
jackson:
mapper:
INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES: false
Jackson Doc 说:
Feature is mostly used to help interoperability with frameworks like Lombok that may automatically generate ConstructorProperties annotation but without necessarily meaning that constructor should be used as Creator for deserialization.