Spring/MockMvc:含文件与非文件的多部件POST请求无法匹配对象类型
问题描述
我定义了如下POST请求接口:
@PostMapping(path = "/metadata/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) ResponseEntity<Void> test(HttpServletResponse response,// @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "partName") Metadata partName, @PathVariable(name = "id") String artifactId) { return null; }
(其中Metadata是一个简单POJO)
随后使用如下测试代码:
@Test public void testMetadata() throws Exception { mockMvc .perform(multipart(HttpMethod.POST, "/metadata/123") .file("file", "file content".getBytes()) .part(getJsonPart()) .contentType(MediaType.MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()); } private @NotNull MockPart getJsonPart() throws JsonProcessingException { byte[] partContent = new ObjectMapper().writeValueAsBytes(new Metadata("foo")); MockPart part = new MockPart("partName", partContent); part.getHeaders().setContentType(MediaType.APPLICATION_JSON); return part; }
测试未通过,返回400而非200,推测是载荷反序列化失败。但当我将PostMapping中的参数类型从Metadata改为Object时,测试可正常运行(该部件会被反序列化为Map)。我需要让控制器能正确反序列化得到Metadata对象,希望得到提示。
为完整起见,附上两个完整文件:
Metadata类与控制器
class Metadata { private String value; public Metadata(String value) { this.value = value; } public String getValue() { return value; } } @RestController public class TestController { @PostMapping(path = "/metadata/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) ResponseEntity<Void> test(HttpServletResponse response,// @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "partName") Metadata partName, @PathVariable(name = "id") String artifactId) { return null; } @PostMapping(path = "/object/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) ResponseEntity<Void> test(HttpServletResponse response, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "partName") Object partName, @PathVariable(name = "id") String artifactId) { return null; } }
测试类
@WebMvcTest(TestController.class) public class TestControllerTest { @Autowired public MockMvc mockMvc; @Test public void failingTest() throws Exception { mockMvc .perform(multipart(HttpMethod.POST, "/metadata/123") .file("file", "file content".getBytes()) .part(getJsonPart()) .contentType(MediaType.MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()); } @Test public void passingTest() throws Exception { mockMvc .perform(multipart(HttpMethod.POST, "/object/123") .file("file", "file content".getBytes()) .part(getJsonPart()) .contentType(MediaType.MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()); } private @NotNull MockPart getJsonPart() throws JsonProcessingException { byte[] partContent = new ObjectMapper().writeValueAsBytes(new Metadata("foo")); MockPart part = new MockPart("partName", partContent); part.getHeaders().setContentType(MediaType.APPLICATION_JSON); return part; } }
解决方案提示
问题核心在于Metadata类缺少Jackson反序列化所需的实例化条件,具体解决方式有两种:
- 添加无参构造函数:Jackson默认需要无参构造来创建对象实例,再通过setter或反射注入属性:
class Metadata { private String value; // 添加无参构造 public Metadata() { } public Metadata(String value) { this.value = value; } public String getValue() { return value; } }
- 给现有构造函数添加Jackson注解:如果不想添加无参构造,可以通过
@JsonCreator和@JsonProperty指定Jackson使用带参构造函数:
class Metadata { private String value; @JsonCreator public Metadata(@JsonProperty("value") String value) { this.value = value; } public String getValue() { return value; } }
两种方式任选其一,都能让Spring MVC正确将JSON类型的RequestPart反序列化为Metadata对象。
内容的提问来源于stack exchange,提问作者ThomasE
相关产品推荐
相关产品推荐

