暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

服务调用Feign

AweSomeBaZinGa 2019-09-12
739


服务调用Feign

1、简介

Feign 的英文表意为“假装,伪装,变形”,是一个http请求调用的轻量级框架(Netflix开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、JAXRS-2.0以及WebSocket),可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。Feign可帮助我们更加便捷、优雅地调用HTTP API。在Spring Cloud中,使用Feign非常简单——只需创建接口,并在接口上添加注解即可。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。Spring Cloud对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Eureka,从而使得Feign的使用更加方便。

2、Feign解决什么问题?

封装了Http调用流程,更适合面向接口化的变成习惯 在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。具体流程如下:

3、Feign是如何设计的?

3.1PHASE 1. 基于面向接口的动态代理方式生成实现类

/**
* 非全局关闭Hystrix @FeignClient(value = "ORGHRSERVICE", configuration = DisableHystrixConfiguration.class)
* 参数必须加@RequestParam 否则服务端会报找不到参数错误
* 如果是url 则必须加@PathVariable
* 如果是传递对象,则接收端必须加@RequestBody
* */
@FeignClient(name = "orghrservice", fallback = OrghrServiceInterfaceHystrix.class)
public interface OrghrServiceInterface {
@RequestMapping(value = "/orghr/user/findOaIdsByIds", method = RequestMethod.POST)
Response findOaUserIdsByIds(@RequestParam("ids") String ids);
}
复制

在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:

public class ReflectiveFeign extends Feign{
///省略部分代码
@Override
public <T> T newInstance(Target<T> target) {
//根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();


for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
// 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);


for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
//省略部分代码
复制

3.2根据Contract协议规则,解析接口类的注解信息,解析成内部表现

List代表有一个Feign客户端的多个方法。

协议解析最终实现的效果就是返回接口调用的定义

GET /repos/foo/myrepo/contributors
HOST XXXX.XXX.XXX
复制

目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping
,@PutMapping
 等,仅支持使用@RequestMapping

3.3基于 RequestBean,动态生成Request

根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象

3.4使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)

Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体

Encoder/ Decoder 实现说明
JacksonEncoder,JacksonDecoder基于 Jackson 格式的持久化转换协议
GsonEncoder,GsonDecoder基于Google GSON 格式的持久化转换协议
SaxEncoder,SaxDecoder基于XML 格式的Sax 库持久化转换协议
JAXBEncoder,JAXBDecoder基于XML 格式的JAXB 库持久化转换协议
ResponseEntityEncoder,ResponseEntityDecoderSpring MVC 基于 ResponseEntity< T > 返回格式的转换协议
SpringEncoder,SpringDecoder基于Spring MVC HttpMessageConverters 一套机制实现的转换协议 ,应用于Spring Cloud 体系中

3.5拦截器负责对请求和返回进行装饰处理

比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
super(properties);
}


@Override
public void apply(RequestTemplate template) {
// 在Header 头部添加相应的数据信息
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
HttpEncoding.DEFLATE_ENCODING);
}
}
复制
@Configuration
public class FeignInterceptor implements RequestInterceptor {


@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("token", "10086");
}


}
复制
/**
* 自定义的请求头处理类,处理服务发送时的请求头;将服务接收到的请求头中的token字段取出来,并设置到新的请求头里面去转发给下游服务;
* 比如A服务收到一个请求,请求头里面包含token字段,A处理时会使用Feign客户端调用B服务,那么token这个字段就会添加到请求头中一并发给B服务;
*
* @author zhangwy
*/
@Configuration
public class FeignHeaderConfig {
Logger logger = LoggerFactory.getLogger(getClass());


@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs != null) {
HttpServletRequest request = attrs.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
/**
* 遍历请求头里面的属性字段,将token添加到新的请求头中转发到下游服务
*/
if ("token".equalsIgnoreCase(name)) {
requestTemplate.header(name, value);
}
}
} else {
logger.warn("FeignHeaderConfig", "获取请求头失败!");
}
/*
// 转发body参数
Enumeration<String> bodyNames = request.getParameterNames();
StringBuffer body = new StringBuffer();
if (bodyNames != null) {
while (bodyNames.hasMoreElements()) {
String name = bodyNames.nextElement();
String value = request.getParameter(name);
body.append(name).append("=").append(value).append("&");
}
}
if (body.length() != 0) {
body.deleteCharAt(body.length() - 1);
requestTemplate.body(body.toString());
}
*/
}
};
}


}


复制

3.6日志记录

在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:

级别说明
NONE不做任何记录,性能最佳,适用于生产环境
BASIC只记录输出Http 方法名称、请求URL、返回状态码和执行时间,适用于生产环境追踪问题
HEADERS记录输出Http 方法名称、请求URL、返回状态码和执行时间 和 Header 信息
FULL记录Request 和Response的Header,Body和一些请求元数据(推荐方式)

Feign的日志打印只会对DEBUG级别做出响应

启用

@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
复制

配置

logging.level.com.sccba.interfaces.feign.* = debug
复制

3.7基于重试器发送HTTP请求

Feign 内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求,以下是Feign核心 代码逻辑:

final class SynchronousMethodHandler implements MethodHandler {


// 省略部分代码


@Override
public Object invoke(Object[] argv) throws Throwable {
//根据输入参数,构造Http 请求。
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 克隆出一份重试器
Retryer retryer = this.retryer.clone();
// 尝试最大次数,如果中间有结果,直接返回
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
复制

重试器有如下几个控制参数:

重试参数说明默认值
period初始重试时间间隔,当请求失败后,重试器将会暂停 初始时间间隔(线程 sleep 的方式)后再开始,避免强刷请求,浪费性能100ms
maxPeriod当请求连续失败时,重试的时间间隔将按照:long interval = (long) (period * Math.pow(1.5, attempt - 1));
 计算,按照等比例方式延长,但是最大间隔时间为 maxPeriod, 设置此值能够避免 重试次数过多的情况下执行周期太长
1000ms
maxAttempts最大重试次数5

FeignClient的默认超时时间为10s,不会开启重试机制,需要自定义配置。重试机制开启后默认为5次,dubbo为3次。

开启自定义超时和重试机制

@Configuration
public class FeignConfigure {
public static int connectTimeOutMillis = 12000;//连接超时时间
public static int readTimeOutMillis = 12000;//读取超时
@Bean
public Request.Options options() {
return new Request.Options(connectTimeOutMillis, readTimeOutMillis);
}


@Bean
public Retryer feignRetryer() {
return new Retryer.Default();
}
}
复制

自定义重试次数

//自定义重试次数
@Bean
public Retryer feignRetryer(){
Retryer retryer = new Retryer.Default(100, 1000, 4);
return retryer;
}
复制

通过配置修改超时时间

feign:
client:
config:
remote-service: #服务名,填写default为所有服务
connectTimeout: 1000
readTimeout: 12000
复制

相关源码:https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Retryer.java

3.8发送Http请求

Feign 真正发送HTTP请求是委托给 feign.Client
 来做的,默认底层通过JDK 的 java.net.HttpURLConnection
 实现了feign.Client
接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端。

引入依赖

<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>10.2.0</version>
</dependency>
复制

开启配置

feign:
httpclient:
enabled: false
okhttp:
enabled: true
复制

配置类

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {


@Autowired
OkHttpLoggingInterceptor okHttpLoggingInterceptor;


@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool())
// .addInterceptor();
.build();
}
}
复制

4、使用

4.1引入依赖

<!-- feign support openfeign默认支持ribbon 和 hystrix,然而不引入会报错 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
复制

4.2启动类

@EnableFeignClients // 开启Feign
@EnableFeignClients(basePackages = "com.sccba.api.client")// 如果feign接口和启动类不在一个包名下
复制

4.3客户端

/**
* 非全局关闭Hystrix @FeignClient(value = "ORGHRSERVICE", configuration = DisableHystrixConfiguration.class)
* 参数必须加@RequestParam 否则服务端会报找不到参数错误
* 如果是url中的占位符参数,则必须加@PathVariable
* 如果是传递对象,则接收端必须加@RequestBody
* */
@FeignClient(name = "orghrservice", fallback = OrghrServiceInterfaceHystrix.class)
public interface OrghrServiceInterface {
@RequestMapping(value = "/orghr/user/findOaIdsByIds", method = RequestMethod.POST)
Response findOaUserIdsByIds(@RequestParam("ids") String ids);
}
复制

参数:@FeignClient(name = "products", url = "http://localhost:7073/products", value = "product", serviceId = "product-server", path = "/products", decode404 = true, fallback = xxx.class)

name:指定 FeignClient 的名称,该属性会作为微服务的名称,用于服务发现,如果没有服务注册中心,可以使用url参数定义接口地址value:同 name 字段互通,注意Sentinel替换Hystrix后好像只能用name而不能用valueserviceId:指定服务ID,每个注册到注册中心上的客户端都会有对应的 serviceId 一般是 spring.application.name,与 name 和 value 互通url:一般用于调试,可以指定一个详细地址(http://localhost:8080/products)path:请求统一路径,可以看成 @RequestMapping("/products")decode404:404 错误时,调用 decoder 进行解码,否则抛出 FeignExceptionfallback:发生错误时,回调 hystrix 类/方法(后面会详细介绍)

4.4使用客户端

@Autowired注入使用即可

4.5Feign和RestTemplate对比

角度RestTemplate + RibbonFeign(自带Ribbon)
可读性、可维护性欠佳(无法从URL直观了解这个远程调用是干什么的)极佳(能在接口上写注释,方法名称也是可读的,能一眼看出这个远程调用是干什么的)
开发体验欠佳极佳
风格一致性欠佳(本地API调用和RestTemplate调用的代码风格截然不同)极佳(完全一致,不点开Feign的接口,根本不会察觉这是一个远程调用而非本地API调用)
性能较好中等(性能是RestTemplate的50%左右;如果为Feign配置连接池,性能可提升15%左右)
灵活性极佳中等(内置功能能满足大多数项目的需求)

4.6Feign配置自定义(细粒度配置)

4.6.1日志

正常配置需要打印日志的步骤如上面描述的

Feign的日志打印只会对DEBUG级别做出响应

启用

@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
复制

配置

logging.level.com.sccba.interfaces.feign.* = debug
复制

在实际使用过程中第一步我们不同环境希望开启不同级别的日志(生产环境NONE,开发环境FULL),所以自定义参数feign.logger.level,根据参数返回不同的日志级别。

@Configuration
@EnableApolloConfig
@Component("feignLoggerConfig")
@Data
public class FeignLoggerConfig {
@Value("${feign.logger.level}")
private String loggerLevel;


@Bean
Logger.Level feignLoggerLevel(){
/*
* feign的日志级别:
* NONE 不做任何记录Default
* BASIC 只记录输出Http方法名称、请求URL、返回状态码、执行时间
* HEADERS 记录输出Http方法名称、请求URL、返回状态码、执行时间、Header信息
* FULL 记录request和response的Header信息。body和一些请求元数据
* */
if("FULL".equals(loggerLevel)){
return Logger.Level.FULL;
}
if("NONE".equals(loggerLevel)){
return Logger.Level.NONE;
}
if("BASIC".equals(loggerLevel)){
return Logger.Level.BASIC;
}
if("HEADERS".equals(loggerLevel)){
return Logger.Level.HEADERS;
}
return Logger.Level.NONE;
}
}


复制

完成这一步后我们还希望不配置logging.level(客户端可能路径不一致,产生多个配置),而是直接记录info级别的日志。

重写feign.Logger

自定义一个Logger类,继承Feign.Logger,将代码中的Debug修改成为Info

public class SccbaFeignLogger extends feign.Logger {
// 这里的logger指的是slf4j的logger
private final Logger logger;


public SccbaFeignLogger() {
this(feign.Logger.class);
}


public SccbaFeignLogger(Class<?> clazz) {
this(LoggerFactory.getLogger(clazz));
}


public SccbaFeignLogger(String name) {
this(LoggerFactory.getLogger(name));
}


SccbaFeignLogger(Logger logger) {
this.logger = logger;
}




@Override
protected void logRequest(String configKey, Level logLevel, Request request) {
if (logger.isInfoEnabled()) {
super.logRequest(configKey, logLevel, request);
}
}


@Override
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime)
throws IOException {
if (logger.isInfoEnabled()) {
return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
}
return response;
}


@Override
protected void log(String configKey, String format, Object... args) {
// Not using SLF4J's support for parameterized messages (even though it
// would be more efficient) because it would
// require the incoming message formats to be SLF4J-specific.
if (logger.isInfoEnabled()) {
logger.info(String.format(methodTag(configKey) + format, args));
}
}
}
复制

自定义Logger类加入配置

@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
Logger SccbaFeign(){
return new SccbaFeignLogger();
}
}
复制

4.6.2其他配置

方式一:指定FeignClient注解的configuration属性,并且将配置放到客户端接口类中实现只对当前客户端生效

@FeignClient(name = "microservice-provider-user", configuration = UserFeignConfig.class)
/**
* 该Feign Client的配置类,注意:
* 1. 该类可以独立出去;
* 2. 该类上也可添加@Configuration声明是一个配置类;
* 配置类上也可添加@Configuration注解,声明这是一个配置类;
* 但此时千万别将该放置在主应用程序上下文@ComponentScan所扫描的包中,
* 否则,该配置将会被所有Feign Client共享,无法实现细粒度配置!
* 个人建议:不加@Configuration注解
*
*/
class UserFeignConfig {
@Bean
public Logger.Level logger() {
return Logger.Level.FULL;
}
}
复制

方式二:配置文件

从Spring Cloud Edgware开始,Feign支持使用属性自定义Feign。对于一个指定名称的Feign Client(例如该Feign Client的名称为feignName
 ),Feign支持如下配置项:

feign:
client:
config:
feignName:
connectTimeout: 5000 # 相当于Request.Options
readTimeout: 5000 # 相当于Request.Options
# 配置Feign的日志级别,相当于代码配置方式中的Logger
loggerLevel: full
# Feign的错误解码器,相当于代码配置方式中的ErrorDecoder
errorDecoder: com.example.SimpleErrorDecoder
# 配置重试,相当于代码配置方式中的Retryer
retryer: com.example.SimpleRetryer
# 配置拦截器,相当于代码配置方式中的RequestInterceptor
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
复制

不建议配置retryer,因为在ribbon中已经进行时重试配置

通用配置

@EnableFeignClients
 注解上有个defaultConfiguration
 属性,我们可以将默认配置写成一个类,然后用defaultConfiguration
 来引用,例如:

@EnableFeignClients(defaultConfiguration = DefaultRibbonConfig.class)
复制

如果想使用配置属性的方式,只需使用类似如下的写法即可

feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
复制

优先级

如果你使用了Java代码配置Feign,同时又使用了配置属性配置Feign,那么使用配置属性的优先级更高。配置属性配置的方式将会覆盖Java代码配置。如果你想修改代码配置方式的优先级,可使用如下属性:feign.client.default-to-properties=false
 。

4.6.3继承

4.7替换client底层为okhttp3

参考3.8即可。

4.8超时和重试

参考3.7即可。

4.9OpenFeign集成Protocol Buffer

Protocol Buffer 是Google的一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

4.10常见问题

4.10.1FeignClient接口如使用@PathVariable
 ,必须指定value属性

@FeignClient("microservice-provider-user")
public interface UserFeignClient {
@RequestMapping(value = "/simple/{id}", method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
...
}
复制

4.10.2构造多参数请求问题

1、有几个参数写几个参数。2、利用map。3、利用requestbody传递实体。

4.10.3Feign集成Hystrix Stream

4.10.5首次请求失败

4.10.6Feign的继承

4.10.7@FeignClient中的@RequestMapping也被SpringMVC加载的问题解决

4.10.8Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题

LocalDate
LocalTime
LocalDateTime
是Java 8开始提供的时间日期API,主要用来优化Java 8以前对于时间日期的处理操作。然而,我们在使用Spring Boot或使用Spring Cloud Feign的时候,往往会发现使用请求参数或返回结果中有LocalDate
LocalTime
LocalDateTime
的时候会发生各种问题。

比如在服务端定义了private LocalDate birthday;这样一个类型,在使用feign调用的时候会出现反序列化失败的错误JSON parse error: Can not construct instance of java.time.LocalDate: no suitable constructor found, can not deserialize from Object value。原因就是实际上默认情况下Spring MVC对于LocalDate
序列化成了一个数组类型,而Feign在调用的时候,还是按照ArrayList
来处理,所以自然无法反序列化为LocalDate
对象了。

解决方法

为了解决上面的问题非常简单,因为jackson也为此提供了一整套的序列化方案,我们只需要在pom.xml
中引入jackson-datatype-jsr310
依赖,具体如下:

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
复制

注意:在设置了spring boot的parent的情况下不需要指定具体的版本,也不建议指定某个具体版本

在该模块中封装对Java 8的时间日期API序列化的实现,其具体实现在这个类中:com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
(注意:一些较早版本封装在这个类中“com.fasterxml.jackson.datatype.jsr310.JSR310Module
)。在配置了依赖之后,我们只需要在上面的应用主类中增加这个序列化模块,并禁用对日期以时间戳方式输出的特性:

@Bean
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
复制

4.10.9Feign实现文件上传

服务提供方(接收文件)

服务提供方的实现比较简单,就按Spring MVC的正常实现方式即可,比如:

@RestController
public class UploadController {
@PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return file.getName();
}
}
复制

服务消费方(发送文件)

在服务消费方由于会使用Feign客户端,所以在这里需要在引入feign对表单提交的依赖,具体如下:

<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
复制

定义文件上传方的应用主类和FeignClient,假设服务提供方的服务名为eureka-feign-upload-server

@FeignClient(value = "upload-server", configuration = UploadService.MultipartSupportConfig.class)
public interface UploadService {


@PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String handleFileUpload(@RequestPart(value = "file") MultipartFile file);


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

在启动了服务提供方之后,尝试在服务消费方编写测试用例来通过上面定义的Feign客户端来传文件,比如:

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UploadTester {


@Autowired
private UploadService uploadService;


@Test
@SneakyThrows
public void testHandleFileUpload() {


File file = new File("upload.txt");
DiskFileItem fileItem = (DiskFileItem) new DiskFileItemFactory().createItem("file",
MediaType.TEXT_PLAIN_VALUE, true, file.getName());


try (InputStream input = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()) {
IOUtils.copy(input, os);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid file: " + e, e);
}


MultipartFile multi = new CommonsMultipartFile(fileItem);


log.info(uploadService.handleFileUpload(multi));
}


}
复制
文章转载自AweSomeBaZinGa,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论