当前位置:   article > 正文

使用jdk序列化的方式实现微服务之间抽象类的数据传输_将multipartfile转换为httpentity对象

将multipartfile转换为httpentity对象

根据日常业务需求微服务之间数据传输对象可能是比较复杂的对象,就以Mybatis-plus QueryWrapper为例,对象中的属性可能存在抽象类的引用,甚至是集合中的抽象类,如果使用json进行传输时,json可能无法转化为对象

QueryWrapper为例,normal变量中3个元素的真实类型是不相同的,但他们都实现了接口ISqlSegment

 

 所以当我们反序列化json字符串成对象的时候,无法知道数组中的三个ISqlSegment对象具体的实现具体是什么,所以会抛出转化异常

 

可以看出 json序列化的方式并没有办法在反序列化时知道到抽象类实例的具体子类型,所以博主采用了jdk的序列化的方式传输(借鉴rabbitMq SimpleMessageConverter 消息传输的思路)

这里引用一个博主的文章代码,本文以feign为例

Feign完美解决服务之间传递文件、传递list,map、对象等情况 - sprouting的个人空间 - OSCHINA - 中文开源技术交流社区

调用端代码(请求方)

在编码对象的时候我们判断如果需要使用jdk编码传输,则使用jdk编码传输,否则默认使用json(上面文章是使用json编码)

  1. package cn.ssq.config;
  2. import cn.ssq.http.model.JdkSerializable;
  3. import cn.ssq.uploadPolicyService.entity.OnlineShop;
  4. import com.alibaba.fastjson.JSON;
  5. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  6. import com.fasterxml.jackson.core.JsonProcessingException;
  7. import com.fasterxml.jackson.databind.ObjectMapper;
  8. import com.fasterxml.jackson.dataformat.xml.XmlMapper;
  9. import feign.RequestTemplate;
  10. import feign.codec.EncodeException;
  11. import feign.codec.Encoder;
  12. import org.springframework.core.io.InputStreamResource;
  13. import org.springframework.core.io.Resource;
  14. import org.springframework.http.HttpEntity;
  15. import org.springframework.http.HttpHeaders;
  16. import org.springframework.http.HttpOutputMessage;
  17. import org.springframework.http.MediaType;
  18. import org.springframework.http.converter.HttpMessageConverter;
  19. import org.springframework.util.LinkedMultiValueMap;
  20. import org.springframework.util.SerializationUtils;
  21. import org.springframework.web.client.RestTemplate;
  22. import org.springframework.web.multipart.MultipartFile;
  23. import java.io.ByteArrayOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.OutputStream;
  27. import java.lang.reflect.Type;
  28. import java.nio.charset.Charset;
  29. import java.util.Arrays;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.Map.Entry;
  33. /**
  34. * @author :LX
  35. * 创建时间: 2020/10/14. 15:06
  36. * 地点:广州
  37. * 目的: 自定义表单编码器。feign 实现多pojo传输与MultipartFile上传 编码器,需配合开启feign自带注解使用
  38. * 用于支持多对象和文件的上传
  39. *
  40. * Encoder的原理就是将每个参数json序列化,设置requestHeader为Multipart/form-data,采用表单请求去请求生成者提供的接口。
  41. * 这个方法能够同时发送多个实体文件,以及MultipartFile[]的数组.
  42. *
  43. * 参考资料:
  44. * https://github.com/pcan/feign-client-test
  45. * 备注说明:
  46. */
  47. public class FeignSpringFormEncoder implements Encoder{
  48. private final List<HttpMessageConverter<?>> converters = new RestTemplate().getMessageConverters();
  49. public static final Charset UTF_8 = Charset.forName("UTF-8");
  50. public FeignSpringFormEncoder() {}
  51. /**
  52. * 实现一个 HttpOutputMessage
  53. */
  54. private class HttpOutputMessageImpl implements HttpOutputMessage{
  55. /**
  56. * 输出流,请求体
  57. */
  58. private final OutputStream body;
  59. /**
  60. * 请求头
  61. */
  62. private final HttpHeaders headers;
  63. public HttpOutputMessageImpl(OutputStream body, HttpHeaders headers) {
  64. this.body = body;
  65. this.headers = headers;
  66. }
  67. @Override
  68. public OutputStream getBody() throws IOException {
  69. return body;
  70. }
  71. @Override
  72. public HttpHeaders getHeaders() {
  73. return headers;
  74. }
  75. }
  76. /**
  77. * 判断是否表单请求
  78. * @param type
  79. * @return
  80. */
  81. static boolean isFormRequest(Type type){
  82. return MAP_STRING_WILDCARD.equals(type);
  83. }
  84. /**
  85. * 内部静态类,保存 MultipartFile 数据
  86. */
  87. static class MultipartFileResource extends InputStreamResource {
  88. /**
  89. * 文件名
  90. */
  91. private final String filename;
  92. /**
  93. * 文件大小
  94. */
  95. private final long size;
  96. /**
  97. * 构造方法
  98. * @param inputStream
  99. * @param filename
  100. * @param size
  101. */
  102. public MultipartFileResource(InputStream inputStream, String filename, long size) {
  103. super(inputStream);
  104. this.filename = filename;
  105. this.size = size;
  106. }
  107. @Override
  108. public String getFilename() {
  109. return this.filename;
  110. }
  111. @Override
  112. public InputStream getInputStream() throws IOException, IllegalStateException {
  113. return super.getInputStream();
  114. }
  115. @Override
  116. public long contentLength(){
  117. return size;
  118. }
  119. }
  120. /**
  121. * 重写编码器
  122. * @param object
  123. * @param bodyType
  124. * @param template
  125. * @throws EncodeException
  126. */
  127. @Override
  128. public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
  129. if (isFormRequest(bodyType)){
  130. final HttpHeaders multipartHeaders = new HttpHeaders();
  131. multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
  132. encodeMultipartFormRequest((Map<Object, ?>) object, multipartHeaders, template);
  133. } else {
  134. final HttpHeaders jsonHeaders = new HttpHeaders();
  135. jsonHeaders.setContentType(MediaType.APPLICATION_JSON);
  136. encodeRequest(object, jsonHeaders, template);
  137. }
  138. }
  139. /**
  140. * 对有文件、表单的进行编码
  141. * @param formMap
  142. * @param multipartHeaders
  143. * @param template
  144. */
  145. private void encodeMultipartFormRequest(Map<Object, ?> formMap, HttpHeaders multipartHeaders, RequestTemplate template){
  146. if (formMap == null){
  147. throw new EncodeException("无法对格式为null的请求进行编码。");
  148. }
  149. LinkedMultiValueMap<Object, Object> map = new LinkedMultiValueMap<>();
  150. //对每个参数进行检查校验
  151. for (Entry<Object, ?> entry : formMap.entrySet()){
  152. Object value = entry.getValue();
  153. //不同的数据类型进行不同的编码逻辑处理
  154. if (isMultipartFile(value)){
  155. //单个文件
  156. map.add(entry.getKey(), encodeMultipartFile((MultipartFile)value));
  157. } else if (isMultipartFileArray(value)){
  158. //多个文件
  159. encodeMultipartFiles(map, (String) entry.getKey(), Arrays.asList((MultipartFile[]) value));
  160. } else {
  161. //普通请求数据
  162. map.add(entry.getKey(), serializeObject(value));
  163. }
  164. }
  165. encodeRequest(map, multipartHeaders, template);
  166. }
  167. /**
  168. * 对请求进行编码
  169. * @param value
  170. * @param requestHeaders
  171. * @param template
  172. */
  173. private void encodeRequest(Object value, HttpHeaders requestHeaders, RequestTemplate template){
  174. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  175. HttpOutputMessage dummyRequest = new HttpOutputMessageImpl(outputStream, requestHeaders);
  176. try {
  177. Class<?> requestType = value.getClass();
  178. MediaType requestContentType = requestHeaders.getContentType();
  179. for (HttpMessageConverter<?> messageConverter : converters){
  180. if (messageConverter.canWrite(requestType, requestContentType)){
  181. ((HttpMessageConverter<Object>) messageConverter).write(value, requestContentType, dummyRequest);
  182. break;
  183. }
  184. }
  185. } catch (IOException e) {
  186. throw new EncodeException("无法对请求进行编码:", e);
  187. }
  188. HttpHeaders headers = dummyRequest.getHeaders();
  189. if (headers != null){
  190. for (Entry<String, List<String>> entry : headers.entrySet()){
  191. template.header(entry.getKey(), entry.getValue());
  192. }
  193. }
  194. /*
  195. 请使用模板输出流。。。如果文件太大,这将导致问题,因为整个请求都将在内存中。
  196. */
  197. template.body(outputStream.toByteArray(), UTF_8);
  198. }
  199. /**
  200. * 序列化对象
  201. * @param obj
  202. * @return
  203. */
  204. private HttpEntity<?> serializeObject(Object obj){
  205. HttpHeaders jsonPartHeaders = new HttpHeaders();
  206. Object content;
  207. if (obj instanceof JdkSerializable){
  208. //需要jdk则使用jdk编码
  209. jsonPartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
  210. content = SerializationUtils.serialize(obj);
  211. }else {
  212. //否则默认使用json
  213. if (obj instanceof String){
  214. content = obj;
  215. }else {
  216. jsonPartHeaders.setContentType(MediaType.APPLICATION_JSON);
  217. content = JSON.toJSONString(obj);
  218. }
  219. }
  220. return new HttpEntity<>(content, jsonPartHeaders);
  221. }
  222. /**
  223. * 编码MultipartFile文件,将其转换为HttpEntity,同时设置 Content-type 为 application/octet-stream
  224. * @param map 当前请求 map.
  225. * @param name 数组字段的名称
  226. * @param fileList 要处理的文件
  227. */
  228. private void encodeMultipartFiles(LinkedMultiValueMap<Object, Object> map, String name, List<? extends MultipartFile> fileList){
  229. HttpHeaders filePartHeaders = new HttpHeaders();
  230. //设置 Content-type
  231. filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
  232. try {
  233. for (MultipartFile file : fileList){
  234. Resource multipartFileResource = new MultipartFileResource(file.getInputStream(), file.getOriginalFilename(), file.getSize());
  235. map.add(name, new HttpEntity<>(multipartFileResource, filePartHeaders));
  236. }
  237. } catch (IOException e) {
  238. throw new EncodeException("无法对请求进行编码:", e);
  239. }
  240. }
  241. /**
  242. * 编码MultipartFile文件,将其转换为HttpEntity,同时设置 Content-type 为 application/octet-stream
  243. * @param file 要编码的文件
  244. * @return
  245. */
  246. private HttpEntity<?> encodeMultipartFile(MultipartFile file){
  247. HttpHeaders filePartHeaders = new HttpHeaders();
  248. //设置 Content-type
  249. filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
  250. try {
  251. Resource multipartFileResource = new MultipartFileResource(file.getInputStream(), file.getOriginalFilename(), file.getSize());
  252. return new HttpEntity<>(multipartFileResource, filePartHeaders);
  253. } catch (IOException e) {
  254. throw new EncodeException("无法对请求进行编码:", e);
  255. }
  256. }
  257. /**
  258. * 判断是否多个 MultipartFile
  259. * @param object
  260. * @return
  261. */
  262. private boolean isMultipartFileArray(Object object){
  263. return object != null && object.getClass().isArray() && MultipartFile.class.isAssignableFrom(object.getClass().getComponentType());
  264. }
  265. /**
  266. * 判断是否MultipartFile文件
  267. * @param object 要判断的对象
  268. * @return
  269. */
  270. private boolean isMultipartFile(Object object){
  271. return object instanceof MultipartFile;
  272. }
  273. }

具体实现代码段

  1. /**
  2. * 序列化对象
  3. * @param obj
  4. * @return
  5. */
  6. private HttpEntity<?> serializeObject(Object obj){
  7. HttpHeaders jsonPartHeaders = new HttpHeaders();
  8. Object content;
  9. if (obj instanceof JdkSerializable){
  10. //需要jdk则使用jdk编码
  11. jsonPartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
  12. content = SerializationUtils.serialize(obj);
  13. }else {
  14. //否则默认使用json
  15. if (obj instanceof String){
  16. content = obj;
  17. }else {
  18. jsonPartHeaders.setContentType(MediaType.APPLICATION_JSON);
  19. content = JSON.toJSONString(obj);
  20. }
  21. }
  22. return new HttpEntity<>(content, jsonPartHeaders);
  23. }
  1. package cn.ssq.http.model;
  2. import java.io.Serializable;
  3. /**
  4. * jdk序列化对象,用于标记需要jdk序列化传输的对象
  5. * @author liufuhao
  6. * @date 2021/10/05
  7. */
  8. public interface JdkSerializable extends Serializable {
  9. }

我们需要重新封装QueryWrapper的DTO对象并实现自定义的JdkSerializable接口

  1. package cn.ssq.dto;
  2. import cn.ssq.http.model.JdkSerializable;
  3. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  4. /**
  5. * mybatis-plus查询条件传输类
  6. */
  7. public class QueryWrapperDTO<T> extends QueryWrapper<T> implements JdkSerializable {
  8. }

服务端代码(接受方)

springmvc默认没有jdk反序列化对象转化实现,所以我们需要自己去实现jdk的反序列化

  1. package cn.ssq.http.converter;
  2. import cn.ssq.http.model.JdkSerializable;
  3. import org.springframework.amqp.utils.SerializationUtils;
  4. import org.springframework.http.HttpInputMessage;
  5. import org.springframework.http.HttpOutputMessage;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.http.converter.AbstractHttpMessageConverter;
  8. import org.springframework.http.converter.HttpMessageNotReadableException;
  9. import org.springframework.remoting.rmi.CodebaseAwareObjectInputStream;
  10. import org.springframework.util.ClassUtils;
  11. import org.springframework.util.StreamUtils;
  12. import java.io.*;
  13. import java.util.LinkedHashSet;
  14. import java.util.Properties;
  15. import java.util.Set;
  16. /**
  17. * jdk序列化转化器
  18. * @author liufuhao
  19. * @date 2021/10/05
  20. */
  21. public class JdkSerializableHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
  22. private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
  23. private final Set<String> whiteListPatterns = new LinkedHashSet();
  24. public JdkSerializableHttpMessageConverter() {
  25. super(new MediaType[]{new MediaType("application", "octet-stream"), MediaType.ALL});
  26. }
  27. /**
  28. * 判断是否支持转化
  29. * @param clazz
  30. * @return
  31. */
  32. public boolean supports(Class<?> clazz) {
  33. Class<?>[] interfaces = clazz.getInterfaces();
  34. for (Class<?> anInterface : interfaces) {
  35. if (anInterface.equals(JdkSerializable.class)){
  36. return true;
  37. }
  38. }
  39. return false;
  40. }
  41. @Override
  42. public Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
  43. long contentLength = inputMessage.getHeaders().getContentLength();
  44. ByteArrayOutputStream bos = new ByteArrayOutputStream(contentLength >= 0L ? (int)contentLength : 4096);
  45. StreamUtils.copy(inputMessage.getBody(), bos);
  46. //使用jdk序列化,需要使用jdk反序列化,原理:读取class池中class对象的并构造实例对象
  47. Object deserialize = SerializationUtils.deserialize(this.createObjectInputStream(new ByteArrayInputStream(bos.toByteArray()), null));
  48. return deserialize;
  49. }
  50. @Override
  51. protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException {
  52. OutputStream os = outputMessage.getBody();
  53. Properties properties = new Properties();
  54. properties.store(os, String.valueOf(SerializationUtils.serialize(obj)));
  55. }
  56. /**
  57. * 获取构建对象流
  58. * @param is
  59. * @param codebaseUrl
  60. * @return
  61. * @throws IOException
  62. */
  63. protected ObjectInputStream createObjectInputStream(InputStream is, String codebaseUrl) throws IOException {
  64. return new CodebaseAwareObjectInputStream(is, this.beanClassLoader, codebaseUrl) {
  65. //根据路径解析获取class对象
  66. protected Class<?> resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException {
  67. Class<?> clazz = super.resolveClass(classDesc);
  68. return clazz;
  69. }
  70. };
  71. }
  72. }
  1. package cn.ssq.http.config;
  2. import cn.ssq.http.converter.JdkSerializableHttpMessageConverter;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.http.converter.HttpMessageConverter;
  5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  6. import java.util.List;
  7. /**
  8. * mvc配置
  9. * @author liufuhao
  10. * @date 2021/10/05
  11. */
  12. @Configuration
  13. public class MyWebConfig implements WebMvcConfigurer {
  14. /**
  15. * 新增反序列化解析器,解析微服务参数
  16. * @param converters
  17. */
  18. @Override
  19. public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
  20. converters.add(new JdkSerializableHttpMessageConverter());
  21. }
  22. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/302824
推荐阅读
相关标签
  

闽ICP备14008679号