OpenFeign
Declarative REST Client
-
声明式 REST 客户端 vs 编程式 REST 客户端 (RestTemplate)
-
注解驱动
-
指定远程地址:@FeignClient
-
指定请求方式:@GetMapping、@PostMapping、@DeleteMapping.....
-
指定携带数据:@RequestHeader、@RequestParam、@RequestBody......
-
指定结果返回:响应模型
-
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
远程调用 - 业务API
启动类添加注解@EnableFeignClients
开启Feign远程调用功能
远程调用接口—@FeignClient
package com.cg.order.feign;
import com.cg.product.bean.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "service-produce") //说明是一个feign客户端
public interface ProductFeignClient {
//MVC注解的两套使用逻辑
//1、标注在控制器上,是接受这样的请求
//2、标注在FeignClient上,是发送这样的请求
@GetMapping("/product/{id}")
Product getProductById(@PathVariable("id") Long id);
}
服务端调用
@Slf4j //日志记录
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
ProductFeignClient productFeignClient;
@Override
public Order createOrder(Long userId, Long productId) {
//使用Feign来完成远程调用
Product product = productFeignClient.getProductById(productId);
Order order = new Order();
order.setId(1l);
//TOOO 总金额需要计算
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("ssss");
order.setAddress("北京");
//远程查询商品列表
order.setProductList(Arrays.asList(product));
return order;
}
是负载均衡的
一些第三方API的调用不需要注册中心
比如气象局、支付宝
以高德地图-天气查询为例
写一个接口
package com.cg.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
//获取路径
//https://restapi.amap.com/v3/weather/weatherInfo?parameters
//weather-client 自定义,无指定
@FeignClient(value = "weather-client",url = "https://restapi.amap.com")//url 精确发送指定地址
public interface WeatherFeignClient {
//RequestHeader请求头 RequestParam请求参数
@GetMapping("/v3/weather/weatherInfo")
String getWeather(@RequestParam("key") String key,
@RequestParam("city") String cityId);
}
测试
package com.cg.order;
import com.cg.order.feign.WeatherFeignClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class WeatherTest {
@Autowired
WeatherFeignClient weatherFeignClient;
@Test
void test01(){
String weather = weatherFeignClient.getWeather(
"cac1133f013a9e9cfeef8b707082db7c",
"110101");
//340700
System.out.println("weather=" + weather);
}
}
————————————————————————————————————————————————
获得:
weather={"status":"1","count":"1","info":"OK","infocode":"10000","lives":[{"province":"北京","city":"东城区","adcode":"110101","weather":"多云","temperature":"32","winddirection":"南","windpower":"≤3","humidity":"43","reporttime":"2025-05-13 16:37:16","temperature_float":"32.0","humidity_float":"43.0"}]}
weather={"status":"1","count":"1","info":"OK","infocode":"10000","lives":[{"province":"安徽","city":"铜陵市","adcode":"340700","weather":"晴","temperature":"30","winddirection":"西南","windpower":"≤3","humidity":"39","reporttime":"2025-05-13 17:01:17","temperature_float":"30.0","humidity_float":"39.0"}]}
调用自定义的业务API
来到要调用的功能处的 Controller,复制方法签名,放进接口
调用第三方API
参照接口文档,怎么发请求就怎样定义
客户端负载均衡与服务器负载均衡区别
1、客户端先去注册中心调用所有地址
自己根据负载均衡算法调用
2、服务端负责负载均衡,客户端不需要管
用自己和用别人的区别
日志
service-order为例
OPen Feign 请求一个API的 详细请求信息
配置文件:application.yml
配置级别,feign所在包下的所有功能组件都在这个级别
logging:
level:
com.cg.order.feign: debug
容器中放置组件:放进配置类
package com.cg.order.config;
import feign.Logger;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class OrderConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
//远程调用就自带 负载均衡
@LoadBalanced //注解式 负载均衡
@Bean //放到容器中
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
运行结果:运行高德地图测试
超时控制
情况:
订单服务 —————————————————商品服务
OpenFeign API
订单服务应用OpenFeign调用商品服务的API
发现:
1、商品服务器宕机; 连接不上
2、API速度慢不返回; 读取不到
解决方法:
加上限时等待
远程调用情况:
1、未超时 , 返回正确结果
2、超时, 中断调用:
-
返回错误信息
-
返回兜底数据(用熔断框架解决)
两种超时时间
connectTimeout 连接超时
控制连接建立的时间
默认值 connectTimeout = 10
readTimeout 读取超时
控制业务处理的时间
默认值 readTimeout = 60
商品服务 修改查询商品业务的睡眠时间改为100
休眠模式
package com.cg.produce.service.impl;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
import com.cg.produce.service.ProductService;
import com.cg.product.bean.Product;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceImpl implements ProductService {
@Override
public Product getProductById(Long productId) {
Product product = new Product();
product.setId(productId);
product.setPrice(new BigDecimal("99"));
product.setProductName("苹果"+productId);
product.setNum(2);
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return product;
}
}
启动一个商品和一个订单,启动创建订单请求
结果:返回错误信息
java.net.SocketTimeoutException: Read timed out
超时配置
修改OpenFeign的两种超时时间
官方文档
https://docs.spring.io/spring-cloud-openfeign/reference/spring-cloud-openfeign.html#spring-cloud-feign-overriding-defaults
新增文件 application-feign.yml
spring:
cloud:
openfeign:
client:
config:
default:
logger-level: full
connect-timeout: 1000
read-timeout: 2000
service-produce:
logger-level: full
connect-timeout: 3000
read-timeout: 5000
修改application.yml
添加: include: feign 激活生产环境,让application-feign.yml文件生效
spring:
profiles:
active: test
include: feign
重试机制
远程调用超时失败后,还可以进行多次尝试,如果某次成功返回ok,如果多次依然失败则结束调用,返回错误
在service-order的配置类
配置类里,放一个重试器
package com.cg.order.config;
import feign.Logger;
import feign.RetryableException;
import feign.Retryer;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class OrderConfig {
//默认值:public Default() {
// this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
// }
@Bean
Retryer retryer(){
return new Retryer.Default();
}
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
//远程调用就自带 负载均衡
@LoadBalanced //注解式 负载均衡
@Bean //放到容器中
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
拦截器
请求拦截器: RequestInterceptor
响应拦截器: ResponseInterceptor
编写 service-order 的拦截器
package com.cg.order.Interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import java.util.UUID;
public class XTokenRequestInterceptor implements RequestInterceptor {
/*
* 请求拦截器
* @param template 请求模板
* */
@Override
public void apply(RequestTemplate template) {
//请求 的 请求头上添加一个X-Token标识 , 值: UUID随机生成一个字符串
template.header("X-Token", UUID.randomUUID().toString());
}
}
拦截器生效方法
1、在openfeign配置里面 application-feign.yml
request-interceptors:
- com.cg.order.Interceptor.XTokenRequestInterceptor
spring:
cloud:
openfeign:
client:
config:
default:
logger-level: full
connect-timeout: 1000
read-timeout: 2000
service-produce:
logger-level: full
connect-timeout: 3000
read-timeout: 5000
request-interceptors:
- com.cg.order.Interceptor.XTokenRequestInterceptor
2、在拦截器上添加@Component注解
package com.cg.order.Interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class XTokenRequestInterceptor implements RequestInterceptor {
/*
* 请求拦截器
* @param template 请求模板
* */
@Override
public void apply(RequestTemplate template) {
System.out.println("XTokenRequestInterceptor ..... " );
//请求 的 请求头上添加一个X-Token标识 , 值: UUID随机生成一个字符串
template.header("X-Token", UUID.randomUUID().toString());
}
}
注释掉睡眠时间
商品服务 service-produce 控制器
package com.cg.produce.controller;
import com.cg.produce.service.ProductService;
import com.cg.product.bean.Product;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController//接受请求并响应数据
public class ProductController {
@Autowired
private ProductService productService;
//查询商品
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") Long productId,
HttpServletRequest request){
String header = request.getHeader("X-Token");
System.out.println("hello ........ token=【"+ header + "】" );
Product product = productService.getProductById(productId);
return product;
}
}
——————————————————————————————————————————————————————
hello ........ token=【a5b33875-a4ea-4a71-a86e-84b0b7804176】
hello ........ token=【980f4c49-ab02-4a2c-bbc1-e8091e2f7a6f】
Fallback-兜底返回
Fallback:兜底返回
注意:此功能需要整合 Sentinel才能实现
远程调用情况:
1、正常 , 返回正确结果
2、超时/错误, 中断调用:
-
返回错误信息
-
返回兜底数据(用熔断框架解决)
为什么需要兜底返回
网络调用的不稳定、对方服务的不稳定,经常发生远程调用失败,
希望返回一些数据而不是错误信息。
可以是:默认数据、缓存数据、假数据 .......
让业务继续走下去
何时需要兜底返回
改善用户体验,不要让用户经常发现崩的样子
商品的远程调用客户端,有可能远程会炸,就写一个兜底返回
实现兜底回调
兜底回调类
package com.cg.order.feign.fallback;
import java.math.BigDecimal;
import com.cg.order.feign.ProductFeignClient;
import com.cg.product.bean.Product;
import org.springframework.stereotype.Component;
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
/*
* 远程调用失败 实现
* */
@Override
public Product getProductById(Long id) {
System.out.println("兜底回调.......");
Product product = new Product();
product.setId(id);
product.setPrice(new BigDecimal("0"));
product.setProductName("未知商品");
product.setNum(0);
return product;
}
}
在feign客户端添加
fallback = ProductFeignClientFallback.class
package com.cg.order.feign;
import com.cg.order.feign.fallback.ProductFeignClientFallback;
import com.cg.product.bean.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//@FeignClient 说明是一个feign客户端
@FeignClient(value = "service-produce", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
//MVC注解的两套使用逻辑
//1、标注在控制器上,是接受这样的请求
//2、标注在FeignClient上,是发送这样的请求
@GetMapping("/product/{id}")
Product getProductById(@PathVariable("id") Long id);
}
添加sentinel依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
application-feign.yml—熔断的功能开启
spring:
cloud:
openfeign:
client:
config:
default:
logger-level: full
connect-timeout: 1000
read-timeout: 2000
service-produce:
logger-level: full
connect-timeout: 3000
read-timeout: 5000
# request-interceptors:
# - com.cg.order.Interceptor.XTokenRequestInterceptor
#feign客户端的 sentinel 做熔断的功能开启
feign:
sentinel:
enabled: true