OpenFeign 如何使用?
引入依赖
<!-- openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
声明式实现
引入依赖之后,我们就可以使用OpenFeign了,其声明式实现如下:
@FeignClient(value = "user-service", configuration = FeignDecoderConfig.class) public interface UserFeignClient { @GetMapping("/api/users/{username}") User getUserByUsername(@PathVariable("username") String username); }
和RestController
类似的。为什么返回值是 User
而不是 Result
,你可以看看我的这篇 Open Feign统一处理返回 文章。通过如上配置我们即可向我们的 user-service
服务发送请求。
当然,大多数情况下,我们会需要 token,即JWT令牌,我们 OpenFeign 如何携带 JWT令牌呢?
拦截器
我们需要设置一个请求拦截器。代码如下:
@Component public class FeignTokenInterceptor implements RequestInterceptor { /** * 请求拦截器 * @param template 请求模板 */ @Override public void apply(RequestTemplate template) { String token = TokenHolder.getToken(); // 从 ThreadLocal 或上下文中获取 // if (token != null) { // template.header("Authorization", token.startsWith("Bearer ") ? token : "Bearer " + token); // } if (token != null) { template.header("token", token); } } }
我用的是 Spring Security,我 JWT 过滤器效验 JWT令牌时,将 JWT 令牌放入了ThreadLocal 中,然后在 OpenFeign的拦截器中,从 ThreadLocal 中取出 token ,并将token 放入请求头中即可。
public class TokenHolder { private static final ThreadLocal<String> tokenThreadLocal = new ThreadLocal<>(); public static void setToken(String token) { tokenThreadLocal.set(token); } public static String getToken() { return tokenThreadLocal.get(); } public static void clear() { tokenThreadLocal.remove(); } }
这就大功告成了,因为 你设置了 @Component
标签,所以 OpenFeign 会自动将这个bean进行注入。之后你的所有 FeignClient 请求都会携带 token 啦。
但是其实在一些情况下有风险,因为你可能调用第三方API。如何调用第三方API呢?代码如下:
//'https://api.qweather.com/v7/weather/now?location=101010100' @FeignClient(value = "weather-server", url = "https://api.qweather.com") public interface WeatherFeignClient { @GetMapping("/v7/weather/now") String getCurrentWeather( @RequestHeader("X-QW-Api-Key")String apiKey, @RequestParam("location") String location); // @Test // void getWeather() { // String currentWeather = weatherFeignClient.getCurrentWeather( // "5c344f4278984bb6a134b6c99f1f2767", "101010100"); // System.out.println(currentWeather); // } }
上面我调用的是 和风天气
的第三方API,你为拦截器设置 @Component
标签 之后,那就是全局有效,包括调用第三方 API 的时候也有效,就可能造成 token
冲突了,因为你的系统token 和第三方api 的 token 肯定不一样,你是携带了自己的token还是 第三方api的token呢?
可能产生的冲突场景
场景 | 结果 | 问题 |
---|---|---|
拦截器先执行,第三方手动后加Token | 请求头包含 两个Token | 第三方服务可能拒绝 |
拦截器后执行,覆盖手动添加的Token | 第三方收到 错误的Token | 认证失败 |
双方使用不同Header字段 | 如 Authorization vs X-API-Key | 可能共存但混乱 |
所以建议进行 OpenFeign的配置。如下:
spring: cloud: openfeign: client: config: default: logger-level: full connect-timeout: 1000 read-timeout: 2000 request-interceptors: - com.dancos.interceptor.FeignTokenInterceptor # 为 远程调用为 user-service 的FeignClient 进行配置,该配置优先级大于默认配置 user-service: connect-timeout: 3000 read-timeout: 1000 request-interceptors: - com.dancos.interceptor.FeignTokenInterceptor
上面不仅配置了 连接超时,读取超时,还配置了 request-interceptors
请求拦截器。
对于我们系统自己的业务,我们可以设置默认拦截器,对于第三方API的服务,我们就为其设置一个单独的啥也不干的拦截器。
其实最主要的是,如果 header 里面的字段一样,那就会出现是否会被覆盖的情况。
如果header里面的字段不一样,那其实影响不大。
兜底回调
我在使用兜底回调时遇到了问题,我的配置如下:
@Component public class UserFeignClientFallback implements UserFeignClient { @Override public User getUserByUsername(String username) { System.out.println("兜底回调"); return new User(); } }
@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class, configuration = FeignDecoderConfig.class ) public interface UserFeignClient { @GetMapping("/api/users/{username}") User getUserByUsername(@PathVariable("username") String username); }
feign: sentinel: enabled: true
如果你要使用兜底回调,记得引入依赖:
<!-- sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
通过上面配置,无法实现兜底回调,一直失败。
原因在于我的配置类,如果你看过我的 这篇 Open Feign统一处理返回(我后续进行了修正),会发现我刚开始创建了一个这样的 bean
:
@Bean public Feign.Builder decodeVoidFeignBuilder() { return Feign.builder().decodeVoid(); }
后续发现这样不可取,这就是导致一直兜底后调失败的罪魁祸首。为什么呢(来自于DeepSeek)?
1. 关键机制冲突
-
Feign.builder().decodeVoid()
创建了一个 全新的 Feign 构建器,它会:-
覆盖 Spring Cloud 自动配置的
Feign.Builder
-
丢失 Sentinel 的装饰器(
SentinelInvocationHandler
) -
导致所有通过该 Builder 创建的 Feign 客户端 失去熔断能力
-
2. Sentinel 集成原理
Spring Cloud Alibaba Sentinel 通过以下方式集成:
// 伪代码:Sentinel 的自动配置 @Bean @ConditionalOnMissingBean // 依赖默认的 Feign.Builder public Feign.Builder feignSentinelBuilder() { return SentinelFeign.builder(); // 关键:注入 Sentinel 逻辑 }
当您自定义 Feign.Builder
时:
-
@ConditionalOnMissingBean
条件不成立 -
Sentinel 的自动配置被跳过
3. decodeVoid()
的特殊性
该方法来自 feign-void
扩展库,会:
-
修改默认的响应处理逻辑
-
干扰 Spring Cloud 的异常转换链
-
使 Sentinel 无法捕获原始异常
总结
其实还要重试机制,日志等等知识,但是我没写,嘿嘿嘿。
。。。
懒得写