RestTemplate 上传文件
在调用第三方Api服务的时候,如果涉及到文件上传,就需要自己通过 Http 客户端发起 Multipart 请求来上传文件。
在 Spring 应用中比较流行的 Http 客户端就是 RestTemplate
。本文将会指导你如何用 RestTemplate
发起 Multipart 请求来上传文件。
服务端
在服务端创建一个用于测试的 FileUploadController
, 它接受来自客户端的 Multipart 文件请求,并且响应文件的相关信息。
package cn.springdoc.demo.controller;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/upload")
public class FileUploadController {
private static final Logger log = LoggerFactory.getLogger(FileUploadController.class);
/**
* 文件上传
* @param file
* @param response
* @return
* @throws IOException
*/
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Map<String, Object> upload (@RequestPart("file") MultipartFile file, HttpServletResponse response) throws IOException {
log.info("文件名称:{}", file.getOriginalFilename());
log.info("表单名称:{}", file.getName());
log.info("文件大小:{}", file.getSize());
log.info("文件类型:{}", file.getContentType());
try(InputStream in = file.getInputStream()){
// 丢弃上传的文件数据
int ret = StreamUtils.drain(in);
log.info("丢弃字节:{}", ret);
}
Map<String, Object> ret = new HashMap<>();
ret.put("originalFilename", file.getOriginalFilename());
ret.put("name", file.getName());
ret.put("size", file.getSize());
ret.put("contentType", file.getContentType());
return ret;
}
}
客户端
package cn.springdoc.test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class MultipartTest {
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
// 消息头
HttpHeaders headers = new HttpHeaders();
// Content-Type 为 multipart/form-data
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// body builder
MultipartBodyBuilder builder = new MultipartBodyBuilder();
// 从磁盘获取到文件
Resource logo = new FileSystemResource("C:\\Users\\KevinBlandy\\Desktop\\512.png");
// 设置表单名称,文件,以及文件类型
builder.part("file", logo, MediaType.IMAGE_PNG);
// 完整的请求体体
MultiValueMap<String, HttpEntity<?>> multipartBody = builder.build();
// 完整的 http 消息
HttpEntity<MultiValueMap<String, HttpEntity<?>>> httpEntity = new HttpEntity<>(multipartBody, headers);
// 发起请求,获取响应
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost:8080/upload", httpEntity, String.class);
System.out.println(responseEntity.getBody());
}
很简单,不到 50 行代码。总结如下:
- 构建
HttpHeaders
,一定要把Content-Type
头设置为MediaType.MULTIPART_FORM_DATA
。 - 构建
MultipartBodyBuilder
,通过这个 builder 来设置要上传的文件对象。 - 文件对象必须是
Resource
接口的实现,Spring 预制了很多不同的实现可用于不同场景,如下:FileSystemResource
用于指定磁盘文件。ClassPathResource
用于指定 classpath 资源文件。ByteArrayResource
用于指定内存中的字节数据。InputStreamResource
用于指定 InputStream 流。
- 通过 header 和 body 构建完整的请求体。
- 发起请求。
如果你的 Spring Web 版本比较高的话,可能会在运行时遇到
ClassNotFoundException: org.reactivestreams.Publisher
异常。Exception in thread "main" java.lang.NoClassDefFoundError: org/reactivestreams/Publisher at cn.springdoc.test.MultipartTest.main(MultipartTest.java:26) Caused by: java.lang.ClassNotFoundException: org.reactivestreams.Publisher at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ... 1 more
此时你需要在客户端添加
reactive-streams
依赖。<!-- https://mvnrepository.com/artifact/org.reactivestreams/reactive-streams --> <dependency> <groupId>org.reactivestreams</groupId> <artifactId>reactive-streams</artifactId> </dependency>
测试
先启动服务器,再执行客户端的 main 方法进行测试,服务器端日志输出如下:
c.s.d.controller.FileUploadController : 文件名称:512.png
c.s.d.controller.FileUploadController : 表单名称:file
c.s.d.controller.FileUploadController : 文件大小:19825
c.s.d.controller.FileUploadController : 文件类型:image/png
c.s.d.controller.FileUploadController : 丢弃字节:19825
客户端获取到的响应如下:
{"size":19825,"name":"file","contentType":"image/png","originalFilename":"512.png"}
上传成功。