springcloud利用feignClient上传文件问题

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

解决方案:

加入maven依赖

<dependency>    
    <groupId>io.github.openfeign.form</groupId>    
    <artifactId>feign-form-spring</artifactId>    
    <version>3.2.2</version>    
</dependency> 
<dependency>    
    <groupId>io.github.openfeign.form</groupId>    
    <artifactId>feign-form</artifactId>    
    <version>3.2.2</version>    
</dependency>  

FeignClient接口里方法参数是文件类型的要用@RequestPart注解,且要设置ContentType为multipart/form-data

@FeignClient(value = "quote-service/api/attendance", configuration = IFeignAttendanceService.MultipartSupportConfig.class)  
public interface IFeignAttendanceService {  
  
	@PostMapping(value = "uploadFile", consumes = MediaType.MULTIPART_FORM_DATA )  
    Object uploadFile(@RequestPart(value = "file") MultipartFile file, @RequestParam("prefixName") String prefixName);  

    @Configuration  
    class MultipartSupportConfig {  
        @Bean  
        public Encoder feignFormEncoder() {  
            return new SpringFormEncoder();  
        }  
    }  
}  

上面的MultipartSupportConfig 类也可以单独作为一个类,这里为了省事,用了内部类。
通过测试后发现,该方案确实可以上传文件,但是当我的接口参数使用实体类接收时,会抛如下异常:

2020-08-01 08:01:00.310 [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  
feign.codec.EncodeException: class cn.erongcai.hrplatform.entity.Quote is not a type supported by this encoder.  
    at feign.codec.Encoder$Default.encode(Encoder.java:90)  
    at feign.form.FormEncoder.encode(FormEncoder.java:87)  
    at feign.form.spring.SpringFormEncoder.encode(SpringFormEncoder.java:64)  
    at feign.ReflectiveFeign$BuildEncodedTemplateFromArgs.resolve(ReflectiveFeign.java:350)  
    at feign.ReflectiveFeign$BuildTemplateByResolvingArgs.create(ReflectiveFeign.java:213)  
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:72)  
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)  
    at com.sun.proxy.$Proxy96.saveQuote(Unknown Source)  
    at cn.erongcai.hrplatform.web.FeignQuoteController.saveQuote(FeignQuoteController.java:45)  
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)  
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  
    at java.lang.reflect.Method.invoke(Method.java:498)  
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)  
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)  
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)  
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)  
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)  
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)  
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)  
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)  
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)  
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)  
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)  
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)  
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)  
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)  
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)  
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)  
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)  
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)  
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)  
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)  
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)  
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)  
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)  
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)  
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)  
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)  
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)  
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)  
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)  
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)  
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)  
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)  
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)  
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)  
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798)  
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434)  
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)  
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)  
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)  
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)  
    at java.lang.Thread.run(Thread.java:748) 

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

public class SpringFormEncoder extends FormEncoder {  

    public SpringFormEncoder() {  
        this(((Encoder) (new feign.codec.Encoder.Default())));  
    }  

    public SpringFormEncoder(Encoder delegate) {  
    	super(delegate);//调用父类的构造方法  
    	MultipartFormContentProcessor processor = (MultipartFormContentProcessor)getContentProcessor(ContentType.MULTIPART);  
    	processor.addWriter(new SpringSingleMultipartFileWriter());  
    	processor.addWriter(new SpringManyMultipartFilesWriter());  
	}  

	public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {  
    	if(!bodyType.equals(org/springframework/web/multipart/MultipartFile)) {  
        	super.encode(object, bodyType, template);//调用FormEncoder对应方法  
        	return;  
    	} else {  
        	MultipartFile file = (MultipartFile)object;  
        	java.util.Map data = Collections.singletonMap(file.getName(), object);  
        	super.encode(data, MAP_STRING_WILDCARD, template);  
        	return;  
    	}  
	}  
}  

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

public FormEncoder() {  
    this(((Encoder) (new feign.codec.Encoder.Default())));  
}  

public FormEncoder(Encoder delegate) {  
    _flddelegate = delegate;  
    List list = Arrays.asList(new ContentProcessor[] {  
        new MultipartFormContentProcessor(delegate), new UrlencodedFormContentProcessor()  
    });  
    processors = new HashMap(list.size(), 1.0F);  
    ContentProcessor processor;  
    for(Iterator iterator = list.iterator(); iterator.hasNext(); processors.put(processor.getSupportedContentType(), processor))  
        processor = (ContentProcessor)iterator.next();  

}  

public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {  
    String contentTypeValue = getContentTypeValue(template.headers());//这里会去到@PostMapping中consumes的值,所以参数需要传对象时指定一下consumes  
    ContentType contentType = ContentType.of(contentTypeValue);//为啥指定consumes,是因为不指定就是application/x-www-form-urlencoded,而且processors中也包含,为啥包含见FormEncoder的构造函数  
    if(!MAP_STRING_WILDCARD.equals(bodyType) || !processors.containsKey(contentType)) {  
        _flddelegate.encode(object, bodyType, template);//_flddelegate是啥呢,是SpringFormEncoder传递过来,也就是new Encoder.Default()  
        return;  
    }  
    Charset charset = getCharset(contentTypeValue);  
    Map data = (Map)object;  
    try {  
        ((ContentProcessor)processors.get(contentType)).process(template, charset, data);  
    }  
    catch(Exception ex) {  
        throw new EncodeException(ex.getMessage());  
    }  
}

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

public interface Encoder  
{  
    public static class Default  
        implements Encoder  
    {  
  
    public void encode(Object object, Type bodyType, RequestTemplate template)  
    {  
        if(bodyType == java/lang/String)  
            template.body(object.toString());  
        else  
        if(bodyType == [B)  
            template.body((byte[])(byte[])object, null);  
        else  
        if(object != null)//当我们用对象传递参数的时候,会走这里  
            throw new EncodeException(String.format("%s is not a type supported by this encoder.", new Object[] {  
                object.getClass()  
            }));  
    }  

    public Default()  
    {  
    }  
}  


public abstract void encode(Object obj, Type type, RequestTemplate requesttemplate)  
    throws EncodeException;  

public static final Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;  

}

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

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

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

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

@Configuration  
class MultipartSupportConfig {  
    @Autowired  
    private ObjectFactory<HttpMessageConverters> messageConverters;  
          
    @Bean  
    public Encoder feignFormEncoder() {  
        return new SpringFormEncoder(new SpringEncoder(messageConverters));  
    }  
}  
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值