使用 RestTemplate 上传文件
「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」
最近在开发中使用到RestTemplate来处理服务间的请求,服务提供的接口需要传入一个文件作为参数解析,而RestTemplate发起的post请求都是将参数放入键值对中并包装到请求体,对于文件的传递该怎么处理呢?
1. RestTemplate的消息转换器
RestTemplate作为restful接口调用的模板类,通过设置的消息转换器类将请求或返回结果转换成需要的类型。使用RestTemplate调用服务传递文件要使用ResourceHttpMessageConverter
转换器。
1.1 文件接收服务接口定义
首先来看一下上传文件是如何与ResourceHttpMessageConverter
产生关系的,如下所示为一个常见的文件上传服务接口定义。
@RestController
@RequestMapping("/file")
public class FileController {
@RequestMapping("/upload")
public String upload(MultipartFile file, String id){
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
String name = file.getOriginalFilename();
long size = file.getSize();
String type = file.getContentType();
String result = "时间:" + time + ",文件名称:" + name + "文件大小:" + size + ",文件类型:" + type + ",id传入值:" + id;
return result;
}
}
Java对应的Web服务中,上传的文件对象通常是使用MultipartFile
对象来接收,该对象继承了org.springframework.core.io
包中的InputStreamSource
接口,这是Spring核心工具包的输入流接口。
1.2 资源请求消息转换器
ResourceHttpMessageConverter
转换器作为RestTemplate中基本的转换器类型,继承了抽象类转换器AbstractHttpMessageConverter
,并通过泛型指定了转换类型为Resource
,进入到Resource
中可以发现,同样实现了InputStreamSource
接口。
//转换器实现
public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> {
...
}
ResourceHttpMessageConverter
转换器可以对Resource
类型进行处理,而Resource
作为Java中的接口,其下的实现类常用的有FileSystemResource
、ByteArrayRestream
、InputStreamResource
。
2. RestTemplate实现上传文件
既然RestTemplate中提供了对文件资源的转换器类型,接下来就看一下如何使用对应的字节流对象进行文件参数的传递。
2.1 FileSystemResource
FileSystemResource作为资源访问类,从名称即可看出是从文件系统中获取资源,通常是根据文件路径或File对象来创建。
根据FileSystemResource的特点,可以使用其读取指定路径下的文件,并将文件作为参数请求到服务中。
//读取本地文件上传
@RequestMapping("/remotRequest")
public String remotRequest(){
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/excel/upload";
MultiValueMap<String,Object> parts = new LinkedMultiValueMap<>();
//读取本地文件 "D:/upload/file";
String filePath = "D:/upload/file/test.xlsx";
FileSystemResource fileSystemResource = new FileSystemResource(filePath);
MultiValueMap<String,Object> params = new LinkedMultiValueMap<>();
HttpHeaders headers = new HttpHeaders();
params.add("file",fileSystemResource);
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String,Object>> requestEntity = new HttpEntity<>(params, headers);
String result = restTemplate.postForObject(url,requestEntity ,String.class);
return result;
}
尽管使用FileSystemResource读取了本地文件进行上传请求,如果文件信息也是从当前服务接口中获取得到,又怎么处理呢?
由于FileSystemResource是操作文件系统资源的,直接用来传递参数不可行,但是可以使用中转的办法,先将接口获取到的文件暂存至临时路径,然后从临时路径中获取文件实现远程传参,并在传参结束后删除文件。
@RequestMapping("/upload")
public String upload(MultipartFile file, String id) throws IOException {
//定义并创建临时文件目录
String tempPath = "D:/file/temp/";
boolean makeSuccess = new File(tempPath).mkdir();
//目录下创建同名文件路径
File tempFile = new File(tempPath + file.getOriginalFilename());
if(tempFile.exists()){
tempFile.delete();
}
if(!tempFile.exists()){
//直接复制文件到指定路径
file.transferTo(tempFile);
}
//此时可以用FileSystemResource读取文件了
FileSystemResource fileSystemResource = new FileSystemResource(tempFile);
//之后可以使用相同方式完成文件传入请求
...
//文件使用结束后删除临时存储的文件
tempFile.delete();
}
如果说这种使用方式太繁琐,需要临时存储再读取,忘记删除时会永久占用磁盘空间,那就可以使用ByteArrayResource和InputStreamResource的方式来传递文件。
2.2 ByteArrayResource
ByteArrayResource在从给定的字节数组中加载内容时非常好用,而不用一味的使用InputStreamResource进行处理。对于将本地内容创建为邮件附件时使用ByteArrayResource是一个好的选择,因为JavaMail需要能够多次读取流数据。
在使用ByteArrayResource对象创建资源对象时,需要重写定义的getFilename()
和contentLength()
两个方法,返回文件的名称和大小信息,否则在文件流传递时无法获取到有效的文件资源而导致最终服务接收文件异常。
@RequestMapping("/upload")
public String upload(MultipartFile file, String id) throws IOException {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/excel/upload";
MultiValueMap<String,Object> params = new LinkedMultiValueMap<>();
ByteArrayResource fileAsResource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename();
}
@Override
public long contentLength() {
return file.getSize();
}
};
params.add("file", fileAsResource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String,Object>> requestEntity = new HttpEntity<>(params, headers);
String result = restTemplate.postForObject(url,requestEntity ,String.class);
return result;
}
2.3 InputStreamResource
教程
Spring官方文档介绍,仅当没有其他特定的资源实现适用时才应使用,在可能的情况下更推荐使用 ByteArrayResource或任何基于文件的Resource实现。
InputStreamResource是Spring中标准的输入流资源对象,读取常见的字节数据,使用InputStreamResource对象将接收的文件内容转成字节流的方式和ByteArrayResource类似,也需要覆写对应的getFilename()
和contentLength()
方法,以此来存放文件的名称和大小信息。
@RequestMapping("/upload")
public String upload(MultipartFile file, String id) throws IOException {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/excel/upload";
MultiValueMap<String,Object> params = new LinkedMultiValueMap<>();
InputStreamResource inputStreamResource = new InputStreamResource(file.getInputStream()){
@Override
public String getFilename() {
return "test";
}
@Override
public long contentLength() {
return file.getSize();
}
};
params.add("file", inputStreamResource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String,Object>> requestEntity = new HttpEntity<>(params, headers);
String result = restTemplate.postForObject(url,requestEntity ,String.class);
return result;
}
3. 总结
RestTemplate的ResourceHttpMessageConverter
消息转换器定义了文件资源的处理,允许RestTemplate将文件作为参数传递,在实际上传文件时要根据实际需要选择不同的资源形式,需要注意的有:
- 如果处理本地资源发起请求,则优先使用FileSystemResource,对于非本地资源也可以使用暂存的方式实现请求操作
- 对于需要多次读取的文件资源,考虑使用ByteArrayResource存放文件字节信息,并实现文件传递的请求
- InputStreamResource是更普遍的资源处理方式,如果其他方式不再适合,就使用InputStreamResource吧
上一篇: 应用程序/json 上传文件