Spring 6.1.0-M4渲染Page对象异常,升级后测试失败求助
Spring 6.1.0-M4升级后Page对象序列化测试失败问题解决
问题描述
将项目从Spring 6.0.10升级至Spring 6.1.0-M4后,控制器返回的Page<PostSummary>对象在测试中出现序列化失败,抛出HttpMessageNotWritableException,根源是UnsupportedOperationException。该功能在旧版本中运行正常,不确定是否需要额外配置。
控制器代码
@RequiredArgsConstructor @RestController @RequestMapping("/posts") @Validated public class PostController { private final PostRepository posts; @GetMapping(value = "", produces = APPLICATION_JSON_VALUE) public ResponseEntity<Page<PostSummary>> getAll(@RequestParam(defaultValue = "") String q, @RequestParam(defaultValue = "") String status, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { var postStatus = StringUtils.hasText(status) ? Status.valueOf(status) : null; var data = this.posts.findAll(Specifications.findByKeyword(q, postStatus), PageRequest.of(page, size)) .map(p -> new PostSummary(p.getTitle(), p.getCreatedAt())); return ok(data); } }
测试代码
@SpringJUnitWebConfig(classes = {Jackson2ObjectMapperConfig.class, WebConfig.class, TestDataConfig.class}) @ActiveProfiles("test") public class PostControllerTestWithMockMvcWebTestClient { @Autowired PostController ctrl; WebTestClient rest; @Autowired PostRepository posts; @BeforeEach public void setup() { this.rest = MockMvcWebTestClient .bindToController(ctrl) .dispatcherServletCustomizer(dispatcherServlet -> dispatcherServlet.setEnableLoggingRequestDetails(true)) .configureClient() .build(); } @Test public void getAllPostsWillBeOk() throws Exception { when(this.posts.findAll(isA(Specification.class), isA(Pageable.class))) .thenReturn(new PageImpl<>( List.of( Post.builder().title("test").content("content of test1").build(), Post.builder().title("test2").content("content of test2").build() ) ) ); this.rest .get() .uri("/posts") .exchange() .expectStatus().isOk(); // .expectBody().jsonPath("$.totalElements").isEqualTo(2); verify(this.posts, times(1)).findAll(isA(Specification.class), isA(Pageable.class)); verifyNoMoreInteractions(this.posts); } }
报错信息
2023-08-22 13:25:10,579 DEBUG [main] org.springframework.core.log.LogFormatUtils: Writing [Page 1 of 1 containing com.example.demo.web.PostSummary instances] 2023-08-22 13:25:10,638 WARN [main] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver: Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.UnsupportedOperationException)] 2023-08-22 13:25:10,666 DEBUG [main] org.springframework.core.log.LogFormatUtils: [4b544732] HTTP GET /posts 2023-08-22 13:25:10,676 DEBUG [main] org.springframework.core.log.LogFormatUtils: [4b544732] [21abda60] Response 500 INTERNAL_SERVER_ERROR 2023-08-22 13:25:10,745 ERROR [main] org.springframework.test.web.reactive.server.ExchangeResult: Request details for assertion failure: > GET /posts > WebTestClient-Request-Id: [1] No content < 500 INTERNAL_SERVER_ERROR Internal Server Error < Content-Type: [application/json] {"content":[{"title":"test","createdAt":null},{"title":"test2","createdAt":null}],"pageable":{"sort":{"empty":true,"sorted":false,"unsorted":true}}} ====================== MockMvc (Server) =============================== MockHttpServletRequest: HTTP Method = GET Request URI = /posts Parameters = {} Headers = [WebTestClient-Request-Id:"1"] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.example.demo.web.PostController Method = com.example.demo.web.PostController#getAll(String, String, int, int) Async: Async started = false Async result = null Resolved Exception: Type = org.springframework.http.converter.HttpMessageNotWritableException ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 500 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"content":[{"title":"test","createdAt":null},{"title":"test2","createdAt":null}],"pageable":{"sort":{"empty":true,"sorted":false,"unsorted":true}}} Forwarded URL = null Redirected URL = null Cookies = [] java.lang.AssertionError: Status expected:<200 OK> but was:<500 INTERNAL_SERVER_ERROR> Expected :200 OK Actual :500 INTERNAL_SERVER_ERROR <Click to see difference> at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59) at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122) at org.springframework.test.web.reactive.server.StatusAssertions.lambda$assertStatusAndReturn$4(StatusAssertions.java:236) at org.springframework.test.web.reactive.server.ExchangeResult.assertWithDiagnostics(ExchangeResult.java:222) at org.springframework.test.web.reactive.server.StatusAssertions.assertStatusAndReturn(StatusAssertions.java:236) at org.springframework.test.web.reactive.server.StatusAssertions.isOk(StatusAssertions.java:68) at com.example.demo.web.PostControllerTestWithMockMvcWebTestClient.getAllPostsWillBeOk(PostControllerTestWithMockMvcWebTestClient.java:63) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
问题原因
测试中创建PageImpl时只传入了内容列表,未指定分页参数(Pageable)和总元素数。PageImpl的默认实现里,未初始化的分页属性(如totalElements、number、size等)对应的getter方法会抛出UnsupportedOperationException。Spring 6.1.0-M4调整了Jackson序列化Page对象的逻辑,会尝试序列化更多分页属性,而旧版本未触发这些属性的序列化,因此升级后出现异常。
解决方案
在测试中创建PageImpl时,传入完整的分页参数,包括Pageable和总元素数。修改测试代码中的返回值部分:
when(this.posts.findAll(isA(Specification.class), isA(Pageable.class))) .thenReturn(new PageImpl<>( List.of( Post.builder().title("test").content("content of test1").build(), Post.builder().title("test2").content("content of test2").build() ), PageRequest.of(0, 10), // 与控制器默认的page和size一致 2 // 总元素数,对应列表中的元素数量 ) );
修改后,PageImpl的所有分页属性都有了有效实现,Jackson序列化时不会再抛出异常,测试即可通过。
内容的提问来源于stack exchange,提问作者Hantsy
相关产品推荐
相关产品推荐

