Feign集成
Feign 集成
功能描述
Feign 集成默认提供了开箱即用的令牌中继功能,此外提供通用响应体解码、通用响应体校验、Nest 路由服务发现功能,可按需启用。
提示
请注意,在设计SDK包时最好能够区分内部SDK和外部SDK
更新记录
v3.0.4
- 通用响应体解码、通用响应体校验
- Nest 路由服务发现功能
v3.1.2
- Nest 服务发现异步缓存优化
GAV 坐标
implementation(commonLibs.blade.feign.spring.boot.starter)<dependency>
<groupId>team.aikero.blade</groupId>
<artifactId>blade-feign-spring-boot-starter</artifactId>
</dependency>快速集成
- 选择合适的 GAV,在项目中添加依赖。
implementation(commonLibs.blade.feign.spring.boot.starter)- 在项目的
application.yaml中添加下方代码。
spring:
config:
import:
- optional:nacos:blade-config <--- 在项目配置之前引入此配置
- optional:nacos:${spring.application.name}详细说明
配置示例
默认情况下,blade-config 提供全局公共配置
若有特殊定制需求可以在application.yml文件中,可以进行以下配置:
blade:
feign:
# Token 中继
tokenRelay:
enabled: true
# 内置包不满足可追加作用范围,或通过 InnerFeign 注解启用中继能力
base-package:
- team.aikero
# 服务发现
discovery:
enabled: false
# 获取路由信息的 http 请求配置,异步缓存
http:
host: http://localhost:8080
uri: /sys-admin/inner/route/conf/list
expire: 30
# 当响应失败时是否抛出异常, 开启时将自动对 @InnerFeign 标注的 FeignClient 进行返回值解包检查
throwOnResponseFailure: false代码示例
令牌中继
目前在我们微服务内部之间的通讯,使用openfeign作为服务间通讯的组件,如下:

当我们一个请求从浏览器发起Http请求携带Token信息请求到网关,网关再转发请求到目标服务时,Token会自动传递下去( 还在一个http链接内),但是如果需要在目标接口所在服务里发起内部feign调用时,则不会再自动传递Token信息到feign接口目标服务
我们每个服务都会有Token鉴权,所以,为了简化服务之间的feign调用,需要对feign做一些拦截处理,将Token中继下去

这个拦截器BearerTokenRelayRequestInterceptor的作用主要是为了在调用其他服务时,将当前请求Token注入到请求头中,用于服务提供方鉴权校验
主要判断逻辑:
- 通过配置feign拦截器,从当前Http信息中获取Token的header信息,传递到feign的请求头中。
- 拦截器内部: 在类上、类接口方法上、feign接口的包名上做判断符合要求即生效

令牌中继详细说明参考:JWT如何在OpenFeign调用中进行令牌中继
代码使用
SDK提供方 新建feign接口
feign SDK包不要引入上面的依赖 那是方便调用者的 不是给SDK包提供者用的
SDK提供方 在feign接口类或feign接口方法上打上注解:
@InnerFeign在方法打注解
@FeignClient( contextId = "CustomerClient", value = Feign1PathConstant.APPLICATION_NAME, path = Feign1PathConstant.CONTEXT_PATH + "/users", ) interface Feign1UserClient { /** * get 测试 * @param id 用户id * @return CustomerInnerVo */ @InnerFeign // 打在方法上 范围小 @GetMapping("/get") fun get(@RequestParam id: Long): DataResponse<UserDto> }打在类上
@FeignClient( contextId = "CustomerClient", value = Feign1PathConstant.APPLICATION_NAME, path = Feign1PathConstant.CONTEXT_PATH + "/users", ) @InnerFeign // 打在类上 所有方法都有效 interface Feign1UserClient { /** * get 测试 * @param id 用户id * @return CustomerInnerVo */ @GetMapping("/get") fun get(@RequestParam id: Long): DataResponse<UserDto> }[或者]SDK调用方 在配置文件上配置包作用范围
如果SDK接口方不做标记,可在SDK调用方服务配置作用包范围
blade:
feign:
token-relay:
base-package: # 内置包不满足可追加作用范围
- team.aikero.blade
- com.demo.xxxxFeign 熔断降级
新版spring-cloud-openfeign移除hystrix依赖和支持。改为支持Spring Cloud CircuitBreaker。 Spring Cloud CircuitBreaker只是断路器的抽象,具体实现由其它框架支持,现在支持的框架如下:
blade没有引用任何断路器的依赖。如果需要熔断降级功能,请自行集成,下面提供了集成样例。
集成Resilience4J
- 开启spring断路器
feign.circuitbreaker.enabled=true - 添加spring断路器实现依赖
依赖
implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j:${latestVersion}")<dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<version>${latestVersion}</version>
</dependency>- 配置Resilience4J 相关参数
# 熔断配置
resilience4j.circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
slidingWindowType: TIME_BASED
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 2s
failureRateThreshold: 30
eventConsumerBufferSize: 10
recordExceptions:
- org.springframework.web.client.HttpServerErrorException
- java.io.IOException
# 超时配置
resilience4j.timelimiter:
configs:
default:
timeoutDuration: 10s # 执行超时时间,默认:1s
cancelRunningFuture: true # 超时后是否取消Future,默认:true集成Sentinel
开发降级接口
和原来一样
?> TODO 还是旧的 待完善
@FeignClient(contextId = "TestClient", value = "testService", fallback = Fallback.class)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class Fallback implements TestClient {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}通用响应体
为了方便各方使用和接口兼容,blade提供了通用响应体,方便沟通和开发。得益于良好的规范,可以通过 Feign 解包减少部分重复判断的代码。
通过自定义解码器,将公共判断逻辑提取,同时支持省略响应体包装。
示例如下:
// feign client
@FeignClient(name = "demo", path = "/demo")
class DemoFeignClient {
/**
* 直接返回 DataResponse.data 内容
*/
@GetMapping("/invoke")
fun invokeWithoutDataResponse(): String
/**
* 返回 DataResponse
*/
@GetMapping("/invoke")
fun invoke(): DataResponse<String>
}
// use
val response:DataResponse<String> = demoFeignClient.invoke()
val content:String = demoFeignClient.invokeWithoutDataResponse()该通用解码器逻辑如下:
blade.feign.throwOnResponseFailure启用响应体校验- 当返回值为
DataResponse时,校验 DataResponse 状态并返回原始 response 对象,当校验失败时,抛出解码异常。 - 当返回值非
DataResponse时,解析 DataResponse 后校验并返回DataResponse.data?,当校验失败时,抛出解码异常。
Nest 服务发现
当前系统部署在多Kubernetes集群环境中,各业务团队服务通过 Nacos 命名空间进行物理隔离。随着业务规模扩展,面临以下核心问题: • 集群归属变更:组织架构调整导致服务集群归属动态变化(如部门重组、资源池再分配)
• 跨部门调用复杂度高:命名空间隔离机制导致服务发现路径固化,跨集群均需手动配置网关地址,无法灵活适应跨NS调用需求
• 基础设施耦合度高:原生服务发现机制(Kubernetes DNS/Nacos Client)与集群物理拓扑强绑定
因此,通过实现自定义 DiscoveryClient 构建路由抽象层,扩展服务发现能力。
注意
大部分场景下,业务代码无需任何改动。
但若 @FeignClient(url="${domain.xxx}") 指定了 url 字段,此时不会经过服务发现。
