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
相关产品推荐
相关产品推荐

