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

Spring Boot 3.2.0升级后Jackson转换Page对象失败求助

Spring Boot 3.0.0 升级到3.2.0后,Controller返回Page对象报JSON序列化错误

将Spring Boot从3.0.0升级到3.2.0后,Controller端点返回org.springframework.data.domain.Page对象时出现JSON序列化失败的500错误。查阅Spring发布说明及相关资料未找到对应解决方案,此前Spring 1.x到2.x的Page JSON结构变更情况与本次不符。疑问点:是否不应直接向客户端返回Spring的Page对象?是什么变更导致了这个问题?

控制器代码

@GetMapping("/reports")
public Page<CAReportSummary> getReportSummaries(
        @RequestParam(name="open_only", defaultValue="true") boolean openOnly,
        @RequestParam("page") Optional<Integer> page, 
        @RequestParam("start") Optional<LocalDate> start, @RequestParam("end") Optional<LocalDate> end, @RequestParam("prop") Optional<String> prop ) {

    if(openOnly) {
        return this.caService.getRequiringAction();
    } 
    
    var defStart = start.orElse(LocalDate.now().minusDays(30));
    var defEnd = end.orElse(LocalDate.now());
    if(prop.isPresent()) {
       return this.caService.getSummariesByLocID(prop.get(), defStart, defEnd, page.orElse(1));
   } 
   return this.caService.getSummariesByDate(defStart, defEnd, page.orElse(1));
} 

Page对象构造示例

@Override
public Page<CAReportSummary> getRequiringAction() {
    var roles = this.authFacade.getUserRoles();
    var props = this.authFacade.getUserWorkLocs();
    var statuses = SecurityUtils.getAccessibleCAStatuses(roles).stream()
            .filter(s -> !(s.equals(CAStatus.CLSD) || s.equals(CAStatus.DSCRD)
                    || s.equals(CAStatus.INVLD)))
            .collect(Collectors.toList());
    var res = this.caReportRepo.findByStatusIdInAndWorkLocIdIn(statuses, props).stream()
            .map(c -> this.mapReport(c)).collect(Collectors.toList());

    return new PageImpl<>(res);
}

错误日志

2024-01-13 10:24:28,982 DEBUG - Using 'application/json', given [application/json, text/plain, */*] and supported [application/json, application/*+json]
2024-01-13 10:24:28,983 DEBUG - Writing [Page 1 of 1 containing com.xxx.xxx.CAReportSummary instances]
2024-01-13 10:24:29,007 DEBUG - Using @ExceptionHandler com.xxx.xxx.controller.ErrorHandler#handleException(Exception, WebRequest)
2024-01-13 10:24:29,016 DEBUG - Using 'application/problem+json', given [application/json, text/plain, */*] and supported [application/problem+json]
2024-01-13 10:24:29,018 DEBUG - Writing [ProblemDetail[type='about:blank', title='Internal Server Error', status=500, detail='Failed to write (truncated)...]
2024-01-13 10:24:29,021 DEBUG - Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.UnsupportedOperationException)]
2024-01-13 10:24:29,021 DEBUG - Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2024-01-13 10:24:29,021 DEBUG - Completed 500 INTERNAL_SERVER_ERROR

问题原因分析

  • Spring Boot 3.2.x中,Spring Data的PageImpl仅传入内容列表的默认构造,会生成未初始化分页元数据的Page对象,其中getSort()、getPageable()等方法会抛出UnsupportedOperationException。
  • 升级后Jackson序列化器对Page对象的序列化逻辑变更,会尝试序列化更多分页相关属性(如sort、pageable),而这些属性在无元数据的PageImpl实例中无法正常访问,导致序列化失败。

解决方案

1. 正确初始化PageImpl的分页元数据

创建PageImpl时传入完整的分页参数,避免空元数据:

// 示例:假设当前是第1页,每页大小为列表长度,总记录数与列表长度一致
Pageable pageable = PageRequest.of(0, res.size());
return new PageImpl<>(res, pageable, res.size());

2. 自定义DTO封装分页结果

避免直接返回Spring Data的Page对象,用自定义DTO封装客户端需要的字段:

// 自定义分页DTO
public class PageDTO<T> {
    private List<T> content;
    private int pageNum;
    private int pageSize;
    private long totalCount;
    
    // 构造器、getter、setter省略
}

// Service层转换
public PageDTO<CAReportSummary> getRequiringAction() {
    // ...原有逻辑获取res列表
    PageDTO<CAReportSummary> dto = new PageDTO<>();
    dto.setContent(res);
    dto.setPageNum(1);
    dto.setPageSize(res.size());
    dto.setTotalCount(res.size());
    return dto;
}

3. 配置Jackson忽略异常属性

如果坚持返回Page对象,可配置Jackson仅序列化必要字段:

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Page.class, new JsonSerializer<Page>() {
            @Override
            public void serialize(Page value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                gen.writeStartObject();
                gen.writeObjectField("content", value.getContent());
                gen.writeNumberField("totalElements", value.getTotalElements());
                gen.writeNumberField("number", value.getNumber());
                gen.writeNumberField("size", value.getSize());
                // 只序列化业务需要的分页字段
                gen.writeEndObject();
            }
        });
        mapper.registerModule(module);
        return mapper;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 13:24:54