OpenFeign

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
 

关闭睡眠和重试器

进行测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值