目录
Imperative REST Client vs Declarative REST Client
命令式 REST 客户端(Imperative REST Client)
声明式 REST客户端 (Declarative REST Client)
常用注解
OpenFeign-远程调用-声明式实现
调用流程
实现流程
OpenFeign-远程调用-第三方API
实现步骤
客户端负载均衡和服务端负载均衡的区别?
OpenFeign-配置进阶-日志
OpenFeign-配置进阶-超时控制
OpenFeign-配置进阶-重试机制
OpenFeign-配置进阶-拦截器
OpenFeign-配置进阶-Fallback
总结
OpenFeign 是 Spring Cloud 生态中的声明式 HTTP 客户端,核心作用是简化微服务间的远程调用—— 无需手动通过 RestTemplate 拼接 URL、处理参数和响应转换,只需通过「接口 + 注解」的方式,就能像调用本地方法一样调用远程服务接口。
Imperative REST Client vs Declarative REST Client
命令式 REST 客户端(Imperative REST Client)
回顾之前用的 RestTemplate、Java 原生 HttpURLConnection、Apache HttpClient 都属于这类。
如下例,你必须手动处理「获取服务实例、拼接 URL、发送 GET 请求、将 JSON 转为 Product 对象」这些步骤,这就是「命令式」。
// 命令式:每一步都要手动实现(服务发现→拼接URL→发送请求→转换响应)
List<ServiceInstance> instances = discoveryClient.getInstances("server-product");
ServiceInstance instance = instances.get(0);
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;
Product product = restTemplate.getForObject(url, Product.class);
声明式 REST客户端 (Declarative REST Client)
它是一种软件设计风格 / 模式 ,而 OpenFeign 就是这种模式的典型实现(具体框架)。无需编写实现细节,只需通过「接口 + 注解」声明你的需求,框架会自动完成底层实现。
如下例,只需要通过「接口 + 注解」声明「我要调用哪个服务的哪个接口,传入什么参数,返回什么结果」,至于「如何发现服务、如何拼接 URL、如何发送请求、如何转换响应」,这些底层细节都由 OpenFeign 框架自动完成,这就是「声明式」。
// 1. 声明需求:我要调用 server-product 服务的 /product/{id} 接口(GET 请求)
@FeignClient(name = "server-product")
public interface ProductFeignClient {
@GetMapping("/product/{id}")
Product getProductById(@PathVariable("id") Long id);
}
// 2. 直接使用:无需关心底层如何实现
Product product = productFeignClient.getProductById(productId);
常用注解
OpenFeign-远程调用-声明式实现
调用流程

实现流程
1.在services的pom文件引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.在service-order的主程序加上@EnableFeignClients注解,开启feign远程调用功能
package com.atguigu.order;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@SpringBootApplication
@EnableDiscoveryClient//开启服务发现功能
@EnableFeignClients//开启feign远程调用功能
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class, args);
}
//1.项目启动就监听配置文件变化
//2.发生变化后拿到变化值
//3.发送邮件
@Bean
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager) {
return args -> {
ConfigService configService = nacosConfigManager.getConfigService();
configService.addListener("service-order.properties", "DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
return Executors.newFixedThreadPool(4);
}
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("变化的配置项:"+ configInfo);
System.out.println("发送邮件");
}
});
System.out.println("==============");
};
}
}
3.在service-order下新建一个类,并加上@FeignClient,表明是基于Feign实现的远程调用客户端。
package com.atguigu.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient//声明这个类是一个FeignClient
public class ProductFeignClient {
}
按上述配置完成之后,就可以具体实现要远程调用的逻辑了。
实现根据商品id远程调用商品信息:
1.在上述步骤3类中加入远程调用逻辑:
package com.atguigu.order.feign;
import com.atguigu.product.bean.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
@FeignClient(value = "service-product")
//声明这个类是一个FeignClient,value值表示要远程调用微服务的名字
public interface ProductFeignClient {
//MVC注解的两套使用逻辑:
//1.标注在Controller上,表示接收这样的请求
//2.标注在FeignClient上,表示发送这样的请求
@GetMapping("/product/{productId}")
Product getProductById(@PathVariable("productId") Long productId);
}
2.实现上述远程调用逻辑之后,在service-order的service实现类中,修改生成订单逻辑。代码如下:
/**
* 创建订单,并远程调用商品服务查询商品信息
* @param productId
* @param userId
* @return
*/
@Override
public Order createOrder(Long productId, Long userId) {
Order order = new Order();
// Product product = getProductFromRemoteWithLoadBalancer(productId);
Product product = productFeignClient.getProductById(productId);
order.setId(1L);
//总金额
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("lqq");
order.setAddress("xian");
//商品列表
order.setProductlist(Arrays.asList( product));
return order;
}
记住要注入ProductFeignClient对象:
@Autowired
ProductFeignClient productFeignClient;
3.重启项目,前后端测试结果如下:

OpenFeign-远程调用-第三方API
第三方调用时,我们可以查阅官方文档,直接获取其调用地址,不需要在注册中心查询什么微服务需要调用什么注册地址。OpenFeign也能做到这个功能。
比如调用墨迹天气:

通过查询接口文档可以获得上述信息。
实现步骤
1.在feign下新建一个类:
package com.atguigu.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value="weather-client", url = "http://aliv18.data.moji.com")
public interface WeatherFeignClient {
@PostMapping("/whapi/json/alicityweather/condition")
String getWeather(@RequestHeader("Authorization") String auth,
@RequestParam("token") String token,
@RequestParam("cityId") String cityId);
}
2.在单元测试下编写一个测试类,来测试该方法是否正确。
package com.atguigu.order;
import com.atguigu.order.feign.WeatherFeignClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class WeatherTest {
@Autowired
WeatherFeignClient weatherFeignClient;
@Test
void test(){
String weather = weatherFeignClient.getWeather("93b7e19861a24c519a7548b17dc16d75",
"50b53ff8dd7d9fa320d3d3ca32cf8ed1", "2182");
System.out.println("weather:" + weather);
}
}
3.测试通过。
客户端负载均衡和服务端负载均衡的区别?

OpenFeign-配置进阶-日志

1.在yml文件里添加:
logging:
level:
com.atguigu.order.feign: debug
2.在配置类里添加:
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
配置完成之后,就可以在日志中看到feign远程调用的全部过程。
OpenFeign-配置进阶-超时控制

超时等待->导致雪崩。

远程调用结果:正确结果、错误信息、兜底数据。
两种超时时间:
- connectTimeout连接超时
- readTimeout读取超时
调用过程:

其中connectTimeout连接超时就是指1.建立连接的时间,readTimeout读取超时就是指2、3、4这个过程的时间。
在OpenFeign中的默认值是:

创建订单时,A向B建立连接,我们将B睡眠100s,在B的sevice实现类中加入以下代码,在前端请求生成订单时,返回连接超时。
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return product;
以上是单独在某个微服务的实现类对连接超时时间进行配置,接下来使用yml文件实现对多个客户端的读取超时时间进行配置:
spring:
cloud:
feign: # 关键:添加 feign 层级,不能少
client:
config:
default: # 全局生效(所有 Feign 客户端)
logger-level: full # 日志级别 full(打印完整请求/响应)
connect-timeout: 1000 # 连接超时 1 秒(也可以写 connectTimeout)
read-timeout: 2000 # 读取超时 2 秒(也可以写 readTimeout)
service-product: # 针对 service-product 服务生效(优先级高于全局)
logger-level: full
connect-timeout: 3000 # 连接超时 3 秒
read-timeout: 5000 # 读取超时 5 秒
OpenFeign-配置进阶-重试机制
远程调用超时失败后,还可以进行多次尝试,如果某次成功返回ok,如果多次依然失败则结束调用,返回错误。

OpenFeign底层默认是从不重试。
我们可以配置重试策略:

方式一:在applicaton-feign.yml添加:
retryer: feign.retryer.Default
方式二:
查看官方文档:

在OrderConfig中,添加:
@Bean
Retryer retryer() {
return new Retryer.Default();
}
项目启动后,前后端联调测试,发现订单发送五次请求之后返回错误信息。
OpenFeign-配置进阶-拦截器

响应拦截器用的不多,现在以请求拦截器为例:

OpenFeign在远程调用之前,想要调用请求拦截器进行拦截,首先编写一个拦截器:
1.在com.atguigu.order下创建一个interceptor.TokenRequestInterceptor类:
package com.atguigu.order.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class TokenRequestInterceptor implements RequestInterceptor {
/**
* 拦截请求,给请求头添加token
* requestTemplate:表示这次请求的详细信息封装到了RequestTemplate请求模板里
* @param requestTemplate 请求模板
*/
@Override
public void apply(RequestTemplate requestTemplate) {
System.out.println("拦截请求,给请求头添加token");
requestTemplate.header("token", UUID.randomUUID().toString());
}
}
2.在商品服务的controller中,将收到的token打印出来:
/**
* 查询商品信息
* @param productId
*/
@GetMapping("/product/{productId}")
public Product getProduct(@PathVariable Long productId,
HttpServletRequest request) {
String token = request.getHeader("token");
System.out.println("查询商品信息,productId = " + productId );
System.out.println("token = " + token);
return productService.getProduct(productId);
}
3.启动项目,查看商品的日志信息,查看是否打印出相应的token的信息:


OpenFeign-配置进阶-Fallback

Fallback:兜底返回
注意:此功能需要整合Sentinel才能实现
兜底数据:默认数据、缓存数据、假数据….
1.在订单服务下的feign新建一个fallback.ProductFeignClientFallback类:
package com.atguigu.order.feign.fallback;
import java.math.BigDecimal;
import com.atguigu.order.feign.ProductFeignClient;
import com.atguigu.product.bean.Product;
import org.springframework.stereotype.Component;
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProductById(Long productId) {
/**
* 只有远程服务调用失败,才会进入此方法
*/
System.out.println("进入兜底回调…");
Product product = new Product();
product.setId(productId);
product.setPrice(new BigDecimal("0"));
product.setProductname("未知商品");
product.setNum(0);
return product;
}
}
2.在ProductFeignClient上加上注解
@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
3.在service-order的pom文件中导入sentinel依赖
<!– 导入sentinel依赖–>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
4.在application-feign.yml文件中配置sentinel
feign:
sentinel:
enabled: true
5.启动项目,前后端联调测试发生在商品服务宕机时进入兜底回调:

总结
1.熟练编写 OpenFeign远程调用客户端
2.熟练配置 OpenFeign客户端属性
- 连接超时
- 读取超时
3.掌握 拦截器 用法
4.掌握 Fallback兜底返回机制及用法





