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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 02:23:11