OpenFeign
参考尚硅谷 https://www.bilibili.com/video/BV1UJc2ezEFU
声明式REST客户端(RestTemplate为编程式REST客户端)
简介
-
Spring Cloud OpenFeign是一种基于Spring Cloud的声明式REST客户端,简化了与HTTP服务交互的过程。
-
它将REST客户端的定义转化为Java接口,并且可以通过注解的方式来声明请求。
调用注册中心的服务接口
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动Feign客户端
(最好标明所在位置)
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.aciu"})
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class,args);
}
}
创建Feign客户端
如果遇到相同name或者url,这样它们就会指向同一个服务器,但每个服务器具有不同的自定义配置。需要设置contextId,避免配置冲突
//name是服务的名称
@FeignClient(name = "service-product",contextId = "productAPI")
public interface ProductFeignClient {
//1、标注在controller上,是接收这样的请求
//2、标注在FeignClient上,是发送这样的请求
@GetMapping("/product/getById")
Product getProduct(@RequestParam Long id);
}
调用第三方接口
GET请求
@FeignClient(name = "thirdPartyService", url = "https://api.example.com")
public interface ThirdPartyServiceClient {
/**
* 带参数的 GET 请求
* @param param1 参数 1
* @param param2 参数 2
* @return 响应结果
*/
@GetMapping("/endpoint")
String getWithParams(@RequestParam("param1") String param1, @RequestParam("param2") String param2);
/**
* 带 Map 参数的 GET 请求
* @param params 参数 Map
* @return 响应结果
*/
@GetMapping("/endpoint")
String getWithMapParams(@RequestParam Map<String, String> params);
}
POST请求
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(name = "thirdPartyService", url = "https://api.example.com")
public interface ThirdPartyServiceClient {
/**
* 带 JSON 体的 POST 请求
* @param requestBody 请求体
* @return 响应结果
*/
@PostMapping("/endpoint")
String postWithJsonBody(@RequestBody Object requestBody);
}
带请求头的请求
常规做法是放在拦截器里声明
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
@FeignClient(name = "thirdPartyService", url = "https://api.example.com")
public interface ThirdPartyServiceClient {
/**
* 带请求头的 GET 请求
* @param authorization 授权头
* @return 响应结果
*/
@GetMapping("/endpoint")
String getWithHeader(@RequestHeader("Authorization") String authorization);
}
Feign的主要配置
Feign日志启用
-
在配置文件启用日志
-
指定 Feign 客户端接口的全限定名,若要控制单独而非全局输出,则细化到类com.aciu.feign.xxx
-
logging: level: com.aciu.feign: DEBUG
-
-
编写全局配置类
-
@Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }
-
Feign超时控制
若出现相同服务和url,可以通过contextId
来区分上下文
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
// 第一个 Feign 客户端
@FeignClient(name = "myFeignClient", contextId = "client1", url = "http://service1.com")
public interface MyFeignClient1 {
@GetMapping("/resource")
String getResource();
}
// 第二个 Feign 客户端
@FeignClient(name = "myFeignClient", contextId = "client2", url = "http://service2.com")
public interface MyFeignClient2 {
@GetMapping("/resource")
String getResource();
}
spring:
cloud:
openfeign:
client:
config:
# 全局客户端配置
default:
connectTimeout: 3000
readTimeout: 3000
loggerLevel: basic
# 为第一个 Feign 客户端配置
client1:
connectTimeout: 3000
readTimeout: 3000
loggerLevel: basic
# 为第二个 Feign 客户端配置
client2:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
也可以采用配置类来设置
public class MyFeignClientConfiguration {
@Bean
public Request.Options options() {
return new Request.Options(5000, 10000); // 连接超时5秒,读取超时10秒
}
}
@FeignClient(name = "service-product",
contextId = "productFeignApiTest",
configuration = MyFeignClientConfiguration.class)
public interface ProductFeignTest {
@GetMapping("/product/all")
List<Product> getAllProduct();
}
Feign重试机制
使用自带重试器
public class FeignConfig {
@Bean
public Retryer feignRetryer() {
// 重试间隔为 100 毫秒,最大重试间隔为 1 秒,最多重试 5 次
return new Retryer.Default(100, 1000, 5);
}
}
@FeignClient(name = "service-product",
contextId = "productFeignApiTest",
configuration = {MyFeignClientConfiguration.class,
RetryFeignConfig.class})
public interface ProductFeignTest {
@GetMapping("/product/all")
List<Product> getAllProduct();
}
Feign拦截器
继承feign.RequestInterceptor
创建自定义拦截器
@Slf4j
public class DecryptFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
log.info("============执行解密拦截器============");
template.header("X-APP-ID","xxxxxx-xxx-xx-xxx-xxxxxxx");
template.header("X-APP-KEY","xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=");
}
}
注册配置文件
public class DecryptFeignConfig {
@Bean
DecryptFeignInterceptor decryptFeignInterceptor(){
return new DecryptFeignInterceptor();
}
}
注入到对应的Feign客户端
@FeignClient(name = "DecryFeignService",
url = "http://1.1.1.1:1111",
configuration = DecryptFeignConfig.class)
public interface DecryptFeignClient {
@PostMapping("/crypt/cryptoRemake")
Object decryptPhoneNum(@RequestBody DecryptDTO decryptDTO);
}
Feign的FallBack
服务之间的调用可能会因为网络问题、服务超时或服务不可用等原因导致请求失败。为了提高系统的容错性和稳定性,Spring Cloud 提供了 Fallback
和 FallbackFactory
机制,允许开发者定义降级逻辑来处理这些异常情况。当使用 Sentinel 作为熔断器时,可以结合 Sentinel 的规则配置和 OpenFeign 的特性来实现更加灵活的降级策略。
降级策略
- 返回固定值或默认值
- 使用缓存数据
- 抛出异常等
启用sentinel
feign:
sentinel:
enabled: true
Fallback 实现
创建一个类实现 Feign 接口,并提供降级逻辑
@Component
public class ExampleServiceFallback implements ExampleServiceClient {
@Override
public String getExample() {
return "Fallback: Service is unavailable.";
}
}
声明fallback
@FeignClient(name = "example-service", fallback = ExampleServiceFallback.class)
public interface ExampleServiceClient {
@GetMapping("/api/example")
String getExample();
}
FallbackFactory实现
创建一个类实现 FallbackFactory<T>
接口,并根据异常信息动态生成降级逻辑:
@Component
public class ExampleServiceFallbackFactory implements FallbackFactory<ExampleServiceClient> {
@Override
public ExampleServiceClient create(Throwable cause) {
return new ExampleServiceClient() {
@Override
public String getExample() {
// 根据异常信息返回自定义的降级逻辑
return "FallbackFactory: Service is unavailable due to " + cause.getMessage();
}
};
}
}
声明FallbackFactory
@FeignClient(name = "example-service", fallbackFactory = ExampleServiceFallbackFactory.class)
public interface ExampleServiceClient {
@GetMapping("/api/example")
String getExample();
}
常问问题
客户端负载均衡与服务端负载均衡的区别