spring cloud feign 上传文件老是报关于not a type supported by this encoder解决方案

项目是用Spring Cloud搭的微服务,使用了eureka,FeignClient。今天在做上传文件功能时,直接使用FeignClient去远程调用注册中心上的上传文件接口,一直报错。

解决方案:

加入maven依赖

[html]  view plain  copy
  1. <dependency>    
  2.     <groupId>io.github.openfeign.form</groupId>    
  3.     <artifactId>feign-form-spring</artifactId>    
  4.     <version>3.2.2</version>    
  5. </dependency>    
  6. <dependency>    
  7.     <groupId>io.github.openfeign.form</groupId>    
  8.     <artifactId>feign-form</artifactId>    
  9.     <version>3.2.2</version>    
  10. </dependency>  
FeignClient接口里方法参数是文件类型的要用@RequestPart注解,且要设置ContentType为multipart/form-data
[java]  view plain  copy
  1. @FeignClient(value = "quote-service/api/attendance", configuration = IFeignAttendanceService.MultipartSupportConfig.class)  
  2. public interface IFeignAttendanceService {  
  3.       
  4.     @PostMapping(value = "uploadFile", consumes = MediaType.MULTIPART_FORM_DATA )  
  5.     Object uploadFile(@RequestPart(value = "file") MultipartFile file, @RequestParam("prefixName") String prefixName);  
  6.   
  7.     @Configuration  
  8.     class MultipartSupportConfig {  
  9.         @Bean  
  10.         public Encoder feignFormEncoder() {  
  11.             return new SpringFormEncoder();  
  12.         }  
  13.     }  
  14. }  

上面的MultipartSupportConfig 类也可以单独作为一个类,这里为了省事,用了内部类。

通过测试后发现,该方案确实可以上传文件,但是当我的接口参数使用实体类接收时,会抛如下异常:

[plain]  view plain  copy
  1. 2018-04-04 09:52:13.410 [http-nio-8720-exec-1] ERROR o.a.c.c.C.[.[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.codec.EncodeException: class cn.erongcai.hrplatform.entity.Quote is not a type supported by this encoder.] with root cause  
  2. feign.codec.EncodeException: class cn.erongcai.hrplatform.entity.Quote is not a type supported by this encoder.  
  3.     at feign.codec.Encoder$Default.encode(Encoder.java:90)  
  4.     at feign.form.FormEncoder.encode(FormEncoder.java:87)  
  5.     at feign.form.spring.SpringFormEncoder.encode(SpringFormEncoder.java:64)  
  6.     at feign.ReflectiveFeign$BuildEncodedTemplateFromArgs.resolve(ReflectiveFeign.java:350)  
  7.     at feign.ReflectiveFeign$BuildTemplateByResolvingArgs.create(ReflectiveFeign.java:213)  
  8.     at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:72)  
  9.     at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)  
  10.     at com.sun.proxy.$Proxy96.saveQuote(Unknown Source)  
  11.     at cn.erongcai.hrplatform.web.FeignQuoteController.saveQuote(FeignQuoteController.java:45)  
  12.     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
  13.     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)  
  14.     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  
  15.     at java.lang.reflect.Method.invoke(Method.java:498)  
  16.     at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)  
  17.     at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)  
  18.     at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)  
  19.     at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)  
  20.     at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)  
  21.     at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)  
  22.     at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)  
  23.     at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)  
  24.     at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)  
  25.     at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)  
  26.     at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)  
  27.     at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)  
  28.     at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)  
  29.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)  
  30.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
  31.     at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)  
  32.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
  33.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
  34.     at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)  
  35.     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)  
  36.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
  37.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
  38.     at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)  
  39.     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)  
  40.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
  41.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
  42.     at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)  
  43.     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)  
  44.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
  45.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
  46.     at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)  
  47.     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)  
  48.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
  49.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
  50.     at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)  
  51.     at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)  
  52.     at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)  
  53.     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)  
  54.     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)  
  55.     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)  
  56.     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)  
  57.     at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)  
  58.     at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)  
  59.     at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798)  
  60.     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434)  
  61.     at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)  
  62.     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)  
  63.     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)  
  64.     at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)  
  65.     at java.lang.Thread.run(Thread.java:748)  

为什么会抛异常呢,查看SpringFormEncoder类的源码:

[java]  view plain  copy
  1. public class SpringFormEncoder extends FormEncoder  
  2. {  
  3.   
  4.     public SpringFormEncoder()  
  5.     {  
  6.         this(((Encoder) (new feign.codec.Encoder.Default())));  
  7.     }  
  8.   
  9.     public SpringFormEncoder(Encoder delegate)  
  10.     {  
  11.         super(delegate);//调用父类的构造方法  
  12.         MultipartFormContentProcessor processor = (MultipartFormContentProcessor)getContentProcessor(ContentType.MULTIPART);  
  13.         processor.addWriter(new SpringSingleMultipartFileWriter());  
  14.         processor.addWriter(new SpringManyMultipartFilesWriter());  
  15.     }  
  16.   
  17.     public void encode(Object object, Type bodyType, RequestTemplate template)  
  18.         throws EncodeException  
  19.     {  
  20.         if(!bodyType.equals(org/springframework/web/multipart/MultipartFile))  
  21.         {  
  22.             super.encode(object, bodyType, template);//调用FormEncoder对应方法  
  23.             return;  
  24.         } else  
  25.         {  
  26.             MultipartFile file = (MultipartFile)object;  
  27.             java.util.Map data = Collections.singletonMap(file.getName(), object);  
  28.             super.encode(data, MAP_STRING_WILDCARD, template);  
  29.             return;  
  30.         }  
  31.     }  
  32. }  

我们可以发现SpringFormEncoder的encode方法当传送的对象不是MultipartFile的时候,就会调用super.encode, 也就是FormEncoder的encode方法。

FormEncoder类的部分源码:

[java]  view plain  copy
  1. public FormEncoder()  
  2.     {  
  3.         this(((Encoder) (new feign.codec.Encoder.Default())));  
  4.     }  
  5.   
  6.     public FormEncoder(Encoder delegate)  
  7.     {  
  8.         _flddelegate = delegate;  
  9.         List list = Arrays.asList(new ContentProcessor[] {  
  10.             new MultipartFormContentProcessor(delegate), new UrlencodedFormContentProcessor()  
  11.         });  
  12.         processors = new HashMap(list.size(), 1.0F);  
  13.         ContentProcessor processor;  
  14.         for(Iterator iterator = list.iterator(); iterator.hasNext(); processors.put(processor.getSupportedContentType(), processor))  
  15.             processor = (ContentProcessor)iterator.next();  
  16.   
  17.     }  
  18.   
  19.     public void encode(Object object, Type bodyType, RequestTemplate template)  
  20.         throws EncodeException  
  21.     {  
  22.         String contentTypeValue = getContentTypeValue(template.headers());//这里会去到@PostMapping中consumes的值,所以参数需要传对象时指定一下consumes  
  23.         ContentType contentType = ContentType.of(contentTypeValue);//为啥指定consumes,是因为不指定就是application/x-www-form-urlencoded,而且processors中也包含,为啥包含见FormEncoder的构造函数  
  24.         if(!MAP_STRING_WILDCARD.equals(bodyType) || !processors.containsKey(contentType))  
  25.         {  
  26.             _flddelegate.encode(object, bodyType, template);//_flddelegate是啥呢,是SpringFormEncoder传递过来,也就是new Encoder.Default()  
  27.             return;  
  28.         }  
  29.         Charset charset = getCharset(contentTypeValue);  
  30.         Map data = (Map)object;  
  31.         try  
  32.         {  
  33.             ((ContentProcessor)processors.get(contentType)).process(template, charset, data);  
  34.         }  
  35.         catch(Exception ex)  
  36.         {  
  37.             throw new EncodeException(ex.getMessage());  
  38.         }  
  39.     }  

FormEncoderr的encode方法当传送的对象是json格式的字符串的时候,就会调用 _flddelegate.encode,即Encoder.Default的encode方法,而这个Encoder.Default的encode方法判断传送的类型不是String或者byte[],就会抛异常

[java]  view plain  copy
  1. public interface Encoder  
  2. {  
  3.     public static class Default  
  4.         implements Encoder  
  5.     {  
  6.   
  7.         public void encode(Object object, Type bodyType, RequestTemplate template)  
  8.         {  
  9.             if(bodyType == java/lang/String)  
  10.                 template.body(object.toString());  
  11.             else  
  12.             if(bodyType == [B)  
  13.                 template.body((byte[])(byte[])object, null);  
  14.             else  
  15.             if(object != null)//当我们用对象传递参数的时候,会走这里  
  16.                 throw new EncodeException(String.format("%s is not a type supported by this encoder."new Object[] {  
  17.                     object.getClass()  
  18.                 }));  
  19.         }  
  20.   
  21.         public Default()  
  22.         {  
  23.         }  
  24.     }  
  25.   
  26.   
  27.     public abstract void encode(Object obj, Type type, RequestTemplate requesttemplate)  
  28.         throws EncodeException;  
  29.   
  30.     public static final Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;  
  31.   
  32. }  

因为我的项目中有很多接口用实体类作为接收参数的,所以上述解决方案无法满足我的要求。所以只能继续各种百度、各种谷歌,终于找到了合适的解决方案。

解决方案一:继续使用前面提到的方案,如果引用该配置类的FeignClient中,没有使用实体类作为参数的接口,则去掉配置类上的注解@Configuration就可以了,去掉注解@Configuration之后,该配置就只对通过configuration属性引用该配置的FeignClient起作用(或者将该文件上传接口单独放到一个FeignClient中,去掉配置类上的注解@Configuration)。

方案一只支持文件上传,如果引用该配置的FeignClient中有使用实体类作为参数接收的接口,则调用该接口时会抛异常。

解决方案二:继续使用前面提到的方案,将配置文件修改为如下:

[java]  view plain  copy
  1. @Configuration  
  2. class MultipartSupportConfig {  
  3.     @Autowired  
  4.     private ObjectFactory<HttpMessageConverters> messageConverters;  
  5.           
  6.     @Bean  
  7.     public Encoder feignFormEncoder() {  
  8.         return new SpringFormEncoder(new SpringEncoder(messageConverters));  
  9.     }  
  10.  }  
方案二既支持文件上传也支持实体类作为参数接收。

转载自:https://blog.csdn.net/qq_32786873/article/details/79756720 希望也能帮助你们

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值