914 字
3 分钟
SpringCloud OpenFeign:声明式服务调用

Spring Cloud OpenFeign#

feign异常处理机制流程图:

  • 服务提供者,会有全局异常包裹
  • 服务消费者,不会有全局异常,只管往外抛出错误,feign感知到以后,走降级熔断。

pom文件#

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-ali.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

开启feign#

如果不在项目扫码范围内,需要添加basePackages指定扫码包

@EnableFeignClients(basePackages = "com.zy.gulimall.common.feign")

开启hystrix#

feign:
hystrix:
enabled: true

Feign logging日志#

配置文件

logging:
level:
#com.eim.feign.ProFeig 为 feign包
com.zy: debug

spring配置Log the headers, body, and metadata for both requests and responses.

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

超时时间#

如何配置好HystrixRibbon的超时时间呢

其实是有套路的。因为Feign的请求:其实是Hystrix+RibbonHystrix在最外层,然后再到Ribbon,最后里面的是http请求。所以说。Hystrix的熔断时间必须大于Ribbon的 ( ConnectTimeout + ReadTimeout )。而如果Ribbon开启了重试机制,还需要乘以对应的重试次数,保证在Ribbon里的请求还没结束时,Hystrix的熔断时间不会超时。

#开启熔断降级 hystrix
feign:
hystrix:
enabled: true
#openfeign超时时间由ribbon控制
ribbon:
# 连接超时时间(ms)
ConnectTimeout: 5000
# 通信超时时间(ms)
ReadTimeout: 5000
# 是否对所有操作重试
OkToRetryOnAllOperations: true
# 同一服务不同实例的重试次数
MaxAutoRetriesNextServer: 1
# 同一实例的重试次数
MaxAutoRetries: 1
#设置超时熔断时间(spring-cloud-starter-openfeign中的HystrixCommandProperties默认为1000毫秒)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000

写feign接口#

  • name : 需要调用的服务名称
  • fallbackFactory: 自定义服务降级工厂
  • decode404 :Feign对于2XX和404 ,都不会走Fallback了。 排除404,已经基本上够用了,如果想把409、400等status也加到例外中,可以重写一下Feign的errorDecoder。
  • configuration 自定义feign配置类
/**
* <p>DESC: 会员feign</p>
* <p>DATE: 2020-04-13 19:24</p>
* <p>VERSION:2.0.0</p>
* <p>@AUTHOR: ZhengYong</p>
*/
@FeignClient(name = "gulimall-member",fallbackFactory = MemberFeignFallbackFactory.class,decode404=true
,configuration = FeignConfiguration.class
)
public interface MemberFeign {
@RequestMapping("member/growthchangehistory/list")
R list(@RequestParam Map<String, Object> params);
}

fallbackFactory#

  • 实现FallbackFactory,泛型填写自己的feign接口
  • 重写create方法
  • BaseFallFactory接收Throwable(其实是异常),调用invoke方法(进行errorCode,message的赋值操作)
  • return new MemberFeign() 重写接口中的服务降级方法
@Slf4j
@Component
public class MemberFeignFallbackFactory implements FallbackFactory<MemberFeign> {
@Override
public MemberFeign create(Throwable throwable) {
//解析错误
BaseFallFactory invoke = new BaseFallFactory(throwable).invoke();
final Integer errorCode = invoke.getErrorCode();
final String message = invoke.getMessage();
return new MemberFeign() {
@Override
public R list(Map<String, Object> params) {
log.error("看见我了没有-------猪猪");
return R.error(errorCode,message);
}
};
}
}

BaseFallFactory#

  • 接收Throwable异常信息,进行信息绑定
  • api业务异常,超时异常,feign失败异常
@Slf4j
public class BaseFallFactory {
private Throwable throwable;
private Integer errorCode;
private String message;
public BaseFallFactory(Throwable throwable) {
this.throwable = throwable;
}
public Integer getErrorCode() {
return errorCode;
}
public String getMessage() {
return message;
}
public BaseFallFactory invoke() {
//是否是api 异常
if (throwable instanceof ApiException) {
errorCode = ((ApiException) throwable).getCode();
message = throwable.getMessage();
}
//是否是请求超时异常
else if (throwable instanceof com.netflix.hystrix.exception.HystrixTimeoutException) {
log.error("error={}", "feign调用超时,抛出RetryableException");
errorCode = HttpStatus.REQUEST_TIMEOUT.value();
message = "Read timed out";
}
else if (throwable instanceof com.netflix.client.ClientException) {
log.error("error={}", "feign调用不可服务,抛出RetryableException");
errorCode = HttpStatus.REQUEST_TIMEOUT.value();
message = "not have available server";
}
//feign调用失败 异常
else {
log.error("error={},错误信息={}", "feign调用失败,抛出FeignException", throwable.getMessage());
errorCode = 400;
message = throwable.getMessage();
}
return this;
}
}

SpringCloud-feign客户端统一处理下游服务自定义异常#

经过尝试和查阅资料,Springcloud处理下游服务的异常是是通过默认的ErrorDecoder实现处理的,最终上游业务获取抛出的异常处理都是FeignException处理,到时上游业务统一异常处理造成困扰,下面解决方式为:

作用:异常解析器,需要实现ErrorDecoder。作用是,当feign服务调用其他服务出现异常,收到的异常数据流处理类.

调用流程 A 调用 B ,

B抛出异常,

全局异常处理,返回json,

熔断器触发,返回feignException

经过默认的ErrorDecoder

执行fallback降级中的方法

ExceptionErrorDecoder

/**
* @author zy
* @version 1.0
* @Description 繼續往上抛出自定義異常
* @date 2021/4/21 16:55
*/
@Slf4j
@Configuration
public class ExceptionErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
try {
Response.Body body = response.body();
if (body != null) {
String str = Util.toString(body.asReader());
log.info("string={}",str);
ExceptionInfo exceptionInfo = JSON.parseObject(str,ExceptionInfo.class);
if(exceptionInfo.getMsg() == null){
JSONObject jsonObject = JSON.parseObject(str);
Integer code = (Integer) jsonObject.get("status");
String msg = (String) jsonObject.get("error");
return new EpException(code,msg);
}
return new EpException(exceptionInfo.getCode(),exceptionInfo.getMsg());
}
} catch (Exception e) {
e.printStackTrace();
}
return FeignException.errorStatus(methodKey, response);
}
}

ExceptionInfo

@Data
public class ExceptionInfo{
private int code;
private String msg;
private Object data;
}

同一个客户端client,配置多个feign#

1、开启bean重复定义

spring.main.allow-bean-definition-overriding=true

2、添加 qualifier 属性

新增一个@FeignClient(value = “parking”),添加 qualifier(限定)

@FeignClient(value = "parking",qualifier = "space")

3、导入时指定 qualifier

@Qualifier("space")
@Autowired
private ParkingSpaceFeign spaceFeign;

Feign跨服务调用导出(下载)接口#

Feign调用#

Feign调用导出接口的方法的出参不是和提供端的方法出入参一样就可以了,一般我们的导出接口都是无返回,直接将内容输出到response中,所以我们调用导出接口的feignt方法需要使用feign提供的Response来接收响应,如下

Feign接口方法#

注意:response为feign的

import feign.Response
/**
* @Author ths
* @Description 导出公司管理数据
* @Date 14:10 2021/4/26
* @Param [commPageDto]
* @return void
**/
@ApiOperation("导出公司管理数据")
@PostMapping( "/epParkManagementCompany/export")
Response export(@RequestBody EpParkManagementCompanyPageRequest commPageDto)

生产者controller方法:#

注意:feign接口上没有HttpServletResponse,生产者有HttpServletResponse

/**
* @Author ths
* @Description 导出公司管理数据
* @Date 14:35 2021/4/27
* @Param [response, commPageDto]
* @return void
**/
@ApiOperation("导出公司管理数据")
@PostMapping( "/export")
public void export(@RequestBody EpParkManagementCompanyPageRequest commPageDto,HttpServletResponse response) {
epParkManagementCompanyService.export(commPageDto,response);
}

消费调用者#

注意:重组response

@ApiOperation("导出公司管理数据")
@PostMapping("/export")
public void export(@RequestBody EpParkManagementCompanyPageRequest commPageDto, HttpServletResponse servletResponse) {
//调用
Response response = parkingFeign.export(commPageDto);
//重组response
Response.Body body = response.body();
for (Object key : response.headers().keySet()) {
List<String> kList = (List) response.headers().get(key);
for (String val : kList) {
servletResponse.setHeader(key.toString(), val);
}
}
try (InputStream inputStream = body.asInputStream();
OutputStream outputStream = servletResponse.getOutputStream()) {
byte[] b = new byte[inputStream.available()];
inputStream.read(b);
outputStream.write(b);
outputStream.flush();
} catch (IOException e) {
log.error("导出Excel失败", e);
}
}

流程 Feign调用其他服务的导出服务,主要流程是通过Feign的Response来接收提供端导出接口的响应,然后通过resp.body()获取响应体,将响应体通过asInputStream方法转为InputStream 输入流,最主要的过程就是将输入流的内容写入到Feign客户端的response输出流中即可

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

SpringCloud OpenFeign:声明式服务调用
https://onecodemaker.cn/posts/springcloud-02-openfeign/
作者
糖糖IT
发布于
2026-03-05
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录