背景:由于Hutool中的HttpUtil没有对应的连接池,所以使用Spring自带的RestTemplate来进行其他系统的Http信息的对接。
问题出现:当对接文件模组时,需要对接上传文件的接口,而我们模块的数据是以字节数组存在的(已经操作过了的字节数组,存在于内存中)接口是以form-data的形式上传的,其中需要上传MultipartFIle,如果使用MultipartFile放入到请求的 from map中,然后再上传这个文件,会报(ByteArrayInputStream no serialized)的错误,也就是没有注入对应的bean的错误。
问题代码:
Mapheaders=newHashMap<String, String>(1); headers.put("Content-Type", "multipart/form-data"); //使用字节数据进行创建文件【方法入参为字节数组】MultipartFilefile=newMockMultiPartFile(filebyte); MultiValueMap<String, Object>form=newLinkedMultiValueMap<>(); form.add("appId", fileDealConstants.getFileDealAppId()); form.add("file", file); form.add("filename", fileName); form.add("fileInvalidDay", "0");
问题报错【截取主要报错】:
org.springframework.http.converter.HttpMessageConversionException: Typedefinitionerror: [simpletype, classjava.io.ByteArrayInputStream]; nestedexceptioniscom.fasterxml.jackson.databind.exc.InvalidDefinitionException: Noserializerfoundforclassjava.io.ByteArrayInputStreamandnopropertiesdiscoveredtocreateBeanSerializer (toavoidexception, disableSerializationFeature.FAIL_ON_EMPTY_BEANS) (throughreferencechain: org.springframework.mock.web.MockMultipartFile["inputStream"]) Causedby: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Noserializerfoundforclassjava.io.ByteArrayInputStreamandnopropertiesdiscoveredtocreateBeanSerializer (toavoidexception, disableSerializationFeature.FAIL_ON_EMPTY_BEANS) (throughreferencechain: org.springframework.mock.web.MockMultipartFile["inputStream"])
百度RestTemplate上传文件的方法,有两种方法解决这种情况。
方法1
使用FileSystemResource进行上传,但是FileSystemResource的创建只能通过 File 或者 文件的路径来生成,也就是说文件已经真实存在磁盘了,所以解决方案是先存到本地然后再进行读取,然后再删除【这种方法看着貌似有点麻烦】,如果需要存到本地的话,还需要判断本地是否有重复的文件,如果删除失败的话,服务器上也会存有许多的垃圾文件,需要定时清除,所以最终采用方法2
此处FileSysteResource的构造函数,只能读取对应的真实存在的文件
publicFileSystemResource(Stringpath) publicFileSystemResource(Filefile) publicFileSystemResource(PathfilePath) publicFileSystemResource(FileSystemfileSystem, Stringpth)
方法2
继承InputStreamResource重写一个构造方法,然后重写getFileName和contentLength即可,可以直接代替MultipartFIle来上传。
参考博客【里面会有一些源码的解析】:https://www.cnblogs.com/shineman-zhang/articles/13070118.html
【写代码过程中的发现】:如果出现InputStream 已经关闭的错误,查看对应的使用方法是不是把InputStream 用完了就关了
CommonInputStreamResource
publicclassCommonInputStreamResourceextendsInputStreamResource { /**** 文件長度*/privateintlength; /**** 文件名稱*/privateStringfileName; publicCommonInputStreamResource(InputStreaminputStream) { super(inputStream); } publicCommonInputStreamResource(InputStreaminputStream, intlength,StringfileName) { super(inputStream); this.length=length; this.fileName=fileName; } /*** 覆写父类方法* 如果不重写这个方法,并且文件有一定大小,那么服务端会出现异常* {@code The multi-part request contained parameter data (excluding uploaded files) that exceeded}** @return*/publicStringgetFilename() { returnthis.fileName; } /*** 覆写父类 contentLength 方法* 因为 {@link org.springframework.core.io.AbstractResource#contentLength()}方法会重新读取一遍文件,* 而上传文件时,restTemplate 会通过这个方法获取大小。然后当真正需要读取内容的时候,发现已经读完,会报如下错误。* <code>* java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times* at org.springframework.core.io.InputStreamResource.getInputStream(InputStreamResource.java:96)* </code>* <p>* ref:com.amazonaws.services.s3.model.S3ObjectInputStream#available()** @return*/publiclongcontentLength() { intestimate=length; returnestimate==0?1 : estimate; } }
无问题的代码:
//设置headerMapheaders=newHashMap<String, String>(1); headers.put("Content-Type", "multipart/form-data"); //根据文件字节数组转对应的 CommonInputStreamResource,然后再上传ByteArrayInputStreaminputStream=newByteArrayInputStream(fileBytes); CommonInputStreamResourcecommonInputStreamResource=newCommonInputStreamResource(inputStream,fileBytes.length,fileName); //必须使用LinkedMultiValueMap传参MultiValueMap<String, Object>form=newLinkedMultiValueMap<>(); form.add("appId", fileDealConstants.getFileDealAppId()); //将文件数据放入到map中form.add("file", commonInputStreamResource); form.add("filename", fileName); form.add("fileInvalidDay", "0"); StringrequestUrl=fileDealConstants.getFileDealUrl() +fileDealConstants.getUploadPath(); ResponseEntity<String>responseEntity=restTemplateUtil.postResponseEntity(headers, fileDealConstants.getFileDealUrl() +fileDealConstants.getUploadPath(), form, newParameterizedTypeReference<String>(){});
RestTemplateUtil部分代码
publicclassRestTemplateUtil { privateRestTemplaterestTemplate; public<T>ResponseEntity<T>postResponseEntity(Map<String, String>headers, Stringurl, Objectbody, ParameterizedTypeReference<T>reference, Object... uriVariables) { MultiValueMap<String, String>map=newHttpHeaders(); if (!ObjectUtils.isEmpty(headers)) { for (Map.Entry<String, String>entry : headers.entrySet()) { map.add(entry.getKey(), entry.getValue()); } } returnrestTemplate.exchange(url, HttpMethod.POST, newHttpEntity<>(body, map), reference, uriVariables); } }