杰克逊自定义反序列化器在 Spring Boot 中不起作用

Posted

技术标签:

【中文标题】杰克逊自定义反序列化器在 Spring Boot 中不起作用【英文标题】:Jackson Custom Deserializer Not Working In Spring Boot 【发布时间】:2019-05-09 02:00:15 【问题描述】:

我为我的实体创建了一个自定义反序列化器,但它不断抛出异常:

我有两个类:AppUser 和 AppUserAvatar

AppUser.java

@Entity
@Table(name = "user")
public class AppUser implements Serializable 

    @Transient
    private static final long serialVersionUID = -3536455219051825651L;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Column(name = "password", nullable = false, length = 256)
    private String password;

    @JsonIgnore
    @Column(name = "is_active", nullable = false)
    private boolean active;

    @JsonIgnore
    @OneToMany(mappedBy = "appUser", targetEntity = AppUserAvatar.class, fetch = FetchType.LAZY)
    private List<AppUserAvatar> appUserAvatars;

    //// Getters and Setters and toString() ////

AppUserAvatar.java

@Entity
@Table(name = "user_avatar")
public class AppUserAvatar extends BaseEntityD implements Serializable 

    @Transient
    private static final long serialVersionUID = 8992425872747011681L;

    @Column(name = "avatar", nullable = false)
    @Digits(integer = 20, fraction = 0)
    @NotEmpty
    private Long avatar;

    @JsonDeserialize(using = AppUserDeserializer.class)
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private AppUser appUser;

    //// Getters and Setters and toString() ////

AppUserDeserializer.java 包 com.nk.accountservice.deserializer;

import com.edoctar.accountservice.config.exception.InputNotFoundException;
import com.edoctar.accountservice.domain.candidate.AppUser;
import com.edoctar.accountservice.service.candidate.AppUserService;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.io.Serializable;

public class AppUserDeserializer extends JsonDeserializer implements Serializable 

    private static final long serialVersionUID = -9012464195937554378L;

    private AppUserService appUserService;

    @Autowired
    public void setAppUserService(AppUserService appUserService) 
        this.appUserService = appUserService;
    

    @Override
    public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException 
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        Long userId = node.asLong();
        System.out.println(node);
        System.out.println(node.asLong());
        AppUser appUser = appUserService.findById(userId);
        System.out.println("appuser: " + appUser);
        if (appUser == null) try 
            throw new InputNotFoundException("User not found!");
         catch (InputNotFoundException e) 
            e.printStackTrace();
            return null;
        

        return appUser;
    

示例 xhr 男孩是:


  "appUser": 1,
  "avatar": 1

每次提交请求都会抛出异常。

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.edoctar.accountservice.domain.candidate.AppUserAvatar["appUser"])]

我发现 appUserService.findById() 方法没有被调用。我真的很困惑。我不知道我哪里错了。对于任何解决方案都将非常有用。谢谢。

【问题讨论】:

解串器中的断点并找到null。我怀疑appUserService 没有被注入,你在那里得到了 NPE @NiVeR 这是观察结果:node: "1"appUserService = null。我不知道为什么它是空的。我用@Service 注释了服务,我想我已经正确注入了它。 【参考方案1】:

更新答案: 您不能使用自动装配的属性,因为您不在 Spring 上下文中。您将 AppUserDeserializer 类作为注释中的引用传递

@JsonDeserialize(using = AppUserDeserializer.class)

在这种情况下,FasterJackson 库创建了AppUserDeserializer 的实例,因此不考虑Autowired 注释。

你可以用一个小技巧来解决你的问题。在AppUserService中添加对spring创建的实例的静态引用:

 @Service
 public AppUserService 

   public static AppUserService instance;

   public AppUserService() 
     // Modify the constructor setting a static variable holding a
     // reference to the instance created by spring
     AppUserService.instance = this;
   

   ...
 

AppUserDeserializer 中使用该引用:

public class AppUserDeserializer extends JsonDeserializer implements Serializable 

    private AppUserService appUserService;

    public AppUserDeserializer() 
      // Set appUserService to the instance created by spring
      this.appUserService = AppUserService.instance;
    

    ...



原始答案:要正确初始化Autowired 属性,您必须注释您的类AppUserDeserializer,否则appUserService 如果您没有使用set 方法显式初始化它,则为空。

尝试用@Component注释AppUserDeserializer

@Component   // Add this annotation
public class AppUserDeserializer extends JsonDeserializer implements Serializable 
   ...

【讨论】:

@Component注释AppUserDeserializer后,appUserService仍然为空; appUserService 上有@Service 吗? 是的,我愿意。 org.springframework.stereotype.Service 注释在服务上。我已经在各种类中注入了这个 appUserService bean,没有问题。 @byteHunt3r 您是否通过显式调用 new AppUserDeserializer() 创建 AppUserDeserializer ?如果你这样做,你就没有使用由 Spring 创建的实例,它已经用 AppUserService 初始化了 我直接将它与目标字段上的@JsonDeserializer 一起使用。例如@JsonDeserialize(using = AppUserDeserializer.class).【参考方案2】:

您可以继续尝试正确注入AppUserService,但在我看来这不是最干净的解决方案。一般来说,我不喜欢使用@Entity 作为通信模型或视图模型的想法。通过这种方式,您将实体耦合到视图模型的生产者/消费者。您基本上是在短路模型部分。

我要做的是在反序列化阶段将 json 的内容映射到不同的类,然后使用这个类来构造相应的实体。

【讨论】:

我只有一个 AppUserService。我什至直接注入了存储库,但结果相同。我想我会摆脱这种方法并使用视图模型。【参考方案3】:

尝试更改这行代码:

private boolean active;

private Boolean active;

布尔原语无法处理空值,可能会导致 NPE。

【讨论】:

在这种情况下不确定这是否重要

以上是关于杰克逊自定义反序列化器在 Spring Boot 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 不为 ZonedDateTime 使用自定义反序列化器

Spring boot 动态/注解自定义 JSON 反序列化器

Spring Boot 1.4 自定义内部 Jackson 反序列化

如何在自定义反序列化器 Spring Boot 中读取路径变量或 URL 参数

如何使用杰克逊制作自定义反序列化器将数字(即部门 ID)转换为部门对象?

Spring Boot:使用自定义序列化器 + 反序列化器消费和生成 XML