如何使用 Jackson 和 Spring Boot 提供自定义反序列化器 [英] How to provide a custom deserializer with Jackson and Spring Boot
问题描述
我有以下三个应用程序:
<块引用>项目 1 保留
- 业务逻辑(Spring Cloud 函数)
- 一个接口IDemoEntity
项目 2
- AWS 特定的处理程序
- IDemoEntity 的一种实现,带有 DynamoDB 特定的注释
- 项目基于 Spring Boot
项目 3
- IDemoEntity 的一种实现,带有 CosmosDB 注释
- 特定于 Azure 的处理程序
项目 1 的类如下所示:
公共接口 IDemoEntity {字符串 getName();void setName(String name);}@成分公共类 StoreFunction 实现 Consumer>{@覆盖公共无效接受(消息 t){System.out.println("存储实体" + t.getPayload().getName());返回;}}
对于项目 2,IDemoEntity 的实现如下所示:
@DynamoDBTable(tableName = "DemoEntity")公共类 DynamoDemoEntity 实现 IDemoEntity {私人字符串名称;@覆盖@DynamoDBHashKey公共字符串 getName() {返回名称;}@覆盖公共无效集名称(字符串名称){this.name = 名称;}}
对于项目 3,IDemoEntity 的实现看起来类似于 DynamoDemoEntity,但带有 CosmosDB 注释.
结构可能看起来有点复杂,但想法如下:
- 一次性实现业务逻辑和数据模型(在项目 1 中)(利用 Spring Cloud 功能)
- 仅为每个平台实现一个包装器项目(我从项目 2 中的 AWS Lambda 开始,但用于 Azure 的项目 3 看起来类似),以及特定于平台的东西(例如实体实现,它需要特定于数据库的注释)
- 使用项目 1 作为依赖项编译特定于平台的项目(例如 AWS Lambda 的项目 2)
我已经试过了,设置基本有效.但是,有一个大问题:
在调用上面的 StoreFunction 时,Jackson 抛出以下异常:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 无法构造`de.margul.awstutorials.springcloudfunction.logic.IDemoEntity` 的实例(没有创建者,如默认构造,存在):抽象类型要么需要映射到具体类型,要么有自定义反序列化器,要么包含额外的类型信息在 [来源: (String)"{"name": "Detlef"}";行:1,列:1]在 com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)在 com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452)在 com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1028)在 com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)在 com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)在 com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)在 de.margul.awstutorials.springcloudfunction.aws.handler.RestSpringBootApiGatewayRequestHandler.deserializeBody(RestSpringBootApiGatewayRequestHandler.java:57)... 3个
这是有道理的,因为 Jackson 不知道,如果 IDemoEntity
它将反序列化接收到的 JSON,那么对于哪个实现.
现在最简单的方法是在 IDemoEntity
上放置一个 @JsonDeserialize(as = DynamoDemoEntity.class)
.
然而,这会破坏我的完整结构:项目 1 将没有信息,它是用哪个平台特定的项目编译的.
任何想法,我如何提供自定义反序列化器(例如作为 Spring bean),但不在项目 1 中进行特定于平台的修改?
首先,您需要创建您的自定义 DynamoDemoEntityDeserializer
,如下所示:
class DynamoDemoEntityDeserializer 扩展 JsonDeserializer{@覆盖公共 DynamoDemoEntity 反序列化(JsonParser p,DeserializationContext ctxt)抛出 IOException,JsonProcessingException {//返回 DynamoDemoEntity 实例;}}
然后你可以像下面这样创建com.fasterxml.jackson.databind.Module
的bean:
@Bean公共模块 dynamoDemoEntityDeserializer() {SimpleModule 模块 = new SimpleModule();module.addDeserializer(IDemoEntity.class, new DynamoDemoEntityDeserializer());返回模块;}
<块引用>
任何 com.fasterxml.jackson.databind.Module 类型的 bean 都会自动注册到自动配置的 Jackson2ObjectMapperBuilder 并应用于它创建的任何 ObjectMapper 实例.当您向应用程序添加新功能时,这提供了一种用于贡献自定义模块的全局机制.
来源:howto-customize-the-jackson-objectmapper
I have the following three applications:
Project 1 holds
- Business logic (Spring Cloud Function)
- An interface IDemoEntity
Project 2
- AWS-specific handler
- One implementation of IDemoEntity, with DynamoDB-specific annotations
- The project is based on Spring Boot
Project 3
- One implementation of IDemoEntity, with CosmosDB annotation
- Azure-specific handler
The classes of project 1 look like this:
public interface IDemoEntity {
String getName();
void setName(String name);
}
@Component
public class StoreFunction implements Consumer<Message<IDemoEntity>> {
@Override
public void accept(Message<IDemoEntity> t) {
System.out.println("Stored entity " + t.getPayload().getName());
return;
}
}
For project 2, the implementation of IDemoEntity looks like this:
@DynamoDBTable(tableName = "DemoEntity")
public class DynamoDemoEntity implements IDemoEntity {
private String name;
@Override
@DynamoDBHashKey
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
For project 3, the implementation of IDemoEntity would look similar to DynamoDemoEntity, but with CosmosDB annotations.
The structure might look a bit complicated, but the idea is the following:
- Implement business logic and data model one time (in project 1) (leveraging Spring Cloud Function)
- Implement just a wrapper project for each platform (I'm starting with AWS Lambda in project 2, but project 3 for Azure would look similar), as well as platform-specific things (like entity implementation, which needs DB-specific annotations)
- Compile the platform-specific project (e. g. project 2 for AWS Lambda) with project 1 as dependency
I've tried it, and the setup works basically. However, there is one big problem:
When calling the StoreFunction above, Jackson throws the following exception:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `de.margul.awstutorials.springcloudfunction.logic.IDemoEntity` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"name": "Detlef"}"; line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1028)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
at de.margul.awstutorials.springcloudfunction.aws.handler.RestSpringBootApiGatewayRequestHandler.deserializeBody(RestSpringBootApiGatewayRequestHandler.java:57)
... 3 more
That makes sense, because Jackson does not know, to which implementation if IDemoEntity
it shall deserialize the received JSON.
The easiest way now would be to place a @JsonDeserialize(as = DynamoDemoEntity.class)
on IDemoEntity
.
However, that would break my complete structure: Project 1 shall have no information, which platform-specific project it is compiled with.
Any ideas, how I could provide a custom deserializer (e. g. as Spring bean), but without making platform-specific modifications in project 1?
Firstly, you need to create your custom DynamoDemoEntityDeserializer
like below:
class DynamoDemoEntityDeserializer extends JsonDeserializer<DynamoDemoEntity> {
@Override
public DynamoDemoEntity deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// return DynamoDemoEntity instance;
}
}
Then you can create bean of com.fasterxml.jackson.databind.Module
like below:
@Bean
public Module dynamoDemoEntityDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(IDemoEntity.class, new DynamoDemoEntityDeserializer());
return module;
}
Any beans of type com.fasterxml.jackson.databind.Module are automatically registered with the auto-configured Jackson2ObjectMapperBuilder and are applied to any ObjectMapper instances that it creates. This provides a global mechanism for contributing custom modules when you add new features to your application.
Source: howto-customize-the-jackson-objectmapper
这篇关于如何使用 Jackson 和 Spring Boot 提供自定义反序列化器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!