You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot升级后@JsonProperty(READ_ONLY)字段反序列化值为null

问题根源

Jackson在2.x版本(对应Spring Boot 2.x)中,@JsonProperty(access = JsonProperty.Access.READ_ONLY)的语义是:序列化时输出该字段,但反序列化时完全跳过对该字段的赋值。这和Spring Boot 1.5.x使用的Jackson旧版本(2.8及以下)行为不同——旧版本中READ_ONLY仅限制客户端通过setter写入,允许通过反射直接给字段赋值。

所以Service1返回的JSON里存在dateCreated、lastUpdated的有效值,但Service2用Jackson反序列化WorkOrder DTO时,这些字段因READ_ONLY标记被跳过,最终为null;而Postman看到的是序列化后的JSON,因此值显示正常。

解决方案

方案1:拆分输入/输出DTO(架构层面最清晰的方案)

分别定义三类DTO,明确区分不同场景的字段范围:

  • WorkOrderCreateRequest:客户端输入专用,仅包含允许修改的字段,不含dateCreated、lastUpdated等只读字段;
  • WorkOrderResponse:服务输出专用,包含所有字段,用于给客户端和内部服务返回数据;
  • 内部调用直接复用WorkOrderResponse即可。

示例代码:

// 客户端输入DTO
class WorkOrderCreateRequest {
    String orderNumber
    String status
    // 其他可写业务字段
}

// 输出/内部调用DTO
class WorkOrderResponse {
    String orderNumber
    String status
    Date dateCreated
    Date lastUpdated
    // 所有业务字段
}

通过DTO拆分天然实现客户端写入限制,彻底避免注解冲突问题。

方案2:启用Jackson的ALLOW_READ_ONLY_PROPERTIES特性(快速修复)

在Service2的ObjectMapper中启用DeserializationFeature.ALLOW_READ_ONLY_PROPERTIES,让Jackson允许反序列化标记为READ_ONLY的字段。

如果使用RestTemplate调用Service1,可自定义RestTemplate配置:

@Configuration
class RestTemplateConfig {
    @Bean
    RestTemplate internalRestTemplate() {
        RestTemplate restTemplate = new RestTemplate()
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter()
        ObjectMapper objectMapper = new ObjectMapper()
        // 启用允许反序列化READ_ONLY字段的特性
        objectMapper.enable(DeserializationFeature.ALLOW_READ_ONLY_PROPERTIES)
        converter.setObjectMapper(objectMapper)
        // 替换默认的Jackson转换器
        restTemplate.getMessageConverters().removeIf { it instanceof MappingJackson2HttpMessageConverter }
        restTemplate.getMessageConverters().add(converter)
        return restTemplate
    }
}

Service2调用时使用这个internalRestTemplate,即可正常反序列化READ_ONLY字段。

方案3:用@JsonIgnore标记setter(兼顾业务限制与反序列化)

保留字段的getter用于序列化输出,在setter上标记@JsonIgnore,这样客户端无法通过JSON写入该字段,但Jackson默认允许通过反射直接给private字段赋值,服务间调用时能正常获取值。

示例DTO:

class WorkOrder {
    private String orderNumber
    private String status
    private Date dateCreated
    private Date lastUpdated

    // 可写字段的getter和setter
    String getOrderNumber() { orderNumber }
    void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber }

    String getStatus() { status }
    void setStatus(String status) { this.status = status }

    // 只读字段的getter,setter加@JsonIgnore限制写入
    Date getDateCreated() { dateCreated }
    @JsonIgnore
    void setDateCreated(Date dateCreated) { this.dateCreated = dateCreated }

    Date getLastUpdated() { lastUpdated }
    @JsonIgnore
    void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated }
}

这种方式既满足了客户端禁止写入的业务要求,又能让服务间调用的反序列化正常赋值。

内容的提问来源于stack exchange,提问作者Prashant Sahi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 10:33:24