使用DTO与MultipartFile数组调用REST接口时的异常排查
Spring WebClient 多部分POST请求失败问题解决
问题描述
尝试通过Spring WebClient发送包含两个部分的POST请求:
- RequestDTO对象
- MultipartFile类型数组
尝试过以下操作但均失败:
- 将文件转为List传递
- 禁用Jackson的
FAIL_ON_EMPTY_BEANS配置
结果抛出CodecException,提示无法序列化sun.nio.ch.ChannelInputStream类;后续尝试将文件转为字符串数组发送,请求发出后后端控制器抛出MissingServletRequestPartException,提示缺少必填的'files'请求部分。
客户端代码
@RequiredArgsConstructor @Slf4j @Service public class RestService { private final WebClient webClient; public ResponseEntity<ResponseDTO> startProcess(MultiValueMap<String, HttpEntity<?>> data) { if (log.isInfoEnabled()) { log.info("Client call \"/endpoint\" with request: {}", data.get("request")); } final var response = this.webClient.post() .uri(RestEndpoints.ENDPOINT) .body(BodyInserters.fromMultipartData(data)) .retrieve() .toEntity(ResponseDTO.class) .block(); if (response != null && response.getStatusCode() == HttpStatus.OK) { return response; } throw new RuntimeException("There is an error"); } public ResponseEntity<ResponseDTO> sendToClient(RequestDTO rq, MultipartFile[] files) { final var multipartBodyBuilder = new MultipartBodyBuilder(); multipartBodyBuilder.part("request", rq); multipartBodyBuilder.part("files", files); final var payload = multipartBodyBuilder.build(); return this.startProcess(payload); } }
序列化异常信息
org.springframework.core.codec.CodecException: Type definition error: [simple type, class sun.nio.ch.ChannelInputStream] ... Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class sun.nio.ch.ChannelInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.Arrays$ArrayList[0]->org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile["inputStream"]) ...
后端控制器代码
@PostMapping(value = "/endpoint", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<ResponseDto> start(@Valid @RequestPart("request") RequestDto request, @RequestPart("files") MultipartFile[] files)
转字符串数组后的异常信息
Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'files' is not present]
错误原因
- 序列化失败:直接传递
MultipartFile数组时,WebClient会尝试用Jackson序列化整个MultipartFile对象,但其中的inputStream属性(sun.nio.ch.ChannelInputStream类型)无对应序列化器,禁用FAIL_ON_EMPTY_BEANS无法解决该问题,因为该类并非空Bean。 - 请求部分不匹配:将文件转为字符串数组后,请求中的'files'部分不再是文件类型,后端期望接收
MultipartFile类型的请求部分,因此无法识别。
解决方案
修改sendToClient方法,逐个处理MultipartFile,将其包装为带正确请求头的HttpEntity,避免直接序列化MultipartFile对象:
public ResponseEntity<ResponseDTO> sendToClient(RequestDTO rq, MultipartFile[] files) { final var multipartBodyBuilder = new MultipartBodyBuilder(); // 为RequestDTO指定JSON媒体类型,确保后端正确解析 multipartBodyBuilder.part("request", rq) .contentType(MediaType.APPLICATION_JSON); // 逐个处理文件,包装为HttpEntity并设置正确的请求头 for (MultipartFile file : files) { try { HttpHeaders fileHeaders = HttpHeaders.writableHttpHeaders(); fileHeaders.setContentType(MediaType.parseMediaType(file.getContentType())); fileHeaders.setContentDispositionFormData("files", file.getOriginalFilename()); HttpEntity<byte[]> fileEntity = new HttpEntity<>(file.getBytes(), fileHeaders); multipartBodyBuilder.part("files", fileEntity); } catch (IOException e) { log.error("Failed to process file: {}", file.getOriginalFilename(), e); throw new RuntimeException("File processing failed", e); } } final var payload = multipartBodyBuilder.build(); return this.startProcess(payload); }
关键要点
- 显式为RequestDTO部分指定
MediaType.APPLICATION_JSON,匹配后端的解析要求。 - 每个文件单独包装为
HttpEntity,包含文件字节内容和正确的请求头:Content-Type:使用文件自身的媒体类型Content-Disposition:指定请求部分名称为"files",并附带文件名
- 避免直接传递
MultipartFile对象/数组,防止触发不必要的序列化操作。
内容的提问来源于stack exchange,提问作者D051P0
相关产品推荐
相关产品推荐

