微服务学习-服务调用组件 OpenFeign 实战

1. OpenFeign 接口方法编写规范

1.1. 在编写 OpenFeign 接口方法时,需要遵循以下规范

1.1.1.1. 接口中的方法必须使用 @RequestMapping、@GetMapping、@PostMapping 等注解声明 HTTP 请求的类型。
1.1.1.2. 方法的参数可以使用 @RequestParam、@RequestHeader、@PathVariable 等注解来制定如何传递 HTTP 请求的参数。
1.1.1.3. 可以使用 @RequestBody 来指定如何传递请求体中的参数。
1.1.1.4. 可以使用 @HearderMap 来传递头部信息。

1.2. 案例演示

1.2.1.1. Get 请求

get 请求,参数全放 URL 中,不建议放 Body,部分浏览器可能会限制不能读取 body 中的数据;

get 请求参数过长的话,也会有问题,适用于参数不长的场景。

目标接口方法:

复制代码
@GetMapping("/getOrderByUserId")
public Result<List<OrderResponse>> getOrderByUserId(@RequestParam("userId") Long userId)

OpenFeign 接口方法

复制代码
@GetMapping("/getOrderByUserId")
Result<List<OrderResponse>> getOrderByUserId(@RequestParam("userId") Long userId);
1.2.1.2. post 请求
1.2.1.2.1. @RequestBody

目标接口方法:

复制代码
@RequestMapping(value = "/post1")
public Result<OrderResponse> post1(@RequestBody OrderRequest orderRequest)

OpenFeign 接口方法

复制代码
@PostMapping("/post1")
Result<OrderResponse> post1(@RequestBody OrderRequest orderRequest);
1.2.1.2.2. URL 后面追加参数

除了放在 body 中的参数,还能直接 url 后面追加参数,@RequestParam 注解

例如:增加参数 token http:/XXXXXXXX?token=xxxxxx

目标接口方法:

复制代码
@RequestMapping(value = "/post2")
public Result<OrderResponse> post2(@RequestBody OrderRequest orderRequest, @RequestParam("token") String token)

OpenFeign 接口方法:

复制代码
@PostMapping("/post2")
Result<OrderResponse> post2(@RequestBody OrderRequest orderRequest, @RequestParam("token") String token);
1.2.1.2.3. url 中加参数

url 中追加参数,参数用 @PathVariable 注解

目标接口方法:

复制代码
@RequestMapping(value = "/post3/{userId}")
public Result<OrderResponse> post3(@RequestBody OrderRequest orderRequest, @PathVariable("userId") Long userId)

OpenFeign 接口方法:

复制代码
@PostMapping("/post3/{userId}")
Result<OrderResponse> post3(@RequestBody OrderRequest orderRequest, @PathVariable("userId") Long userId);

2. OpenFeign 的调用流程

3. OpenFeign 常用扩展点配置

openFeign 提供了很多的扩展机制,让用户可以更加灵活的使用。

3.1. 测试环境

速通版:git checkout v2.0.0 版本:

icoolkj-microservices-code 标签 - Gitee.com

会员服务调用订单服务。

3.2. 日志配置

配置 Feign 的日志,让 Feign 把请求信息输出,方便查找问题等。

日志级别有4 种:

  • NONE【性能最佳,默认值】:不记录任何日志。
  • BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
  • HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
  • FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。
3.2.1.1. 配置方式
3.2.1.1.1. Java Bean 方式

全局生效:利用 @Configuration 实现全局生效,对所有的微服务调用者都生效。

    1. 定义一个配置类,指定日志级别

    // 注意: 此处配置@Configuration注解就会全局生效,如果想指定对应微服务生效,就不能配置@Configuration
    @Configuration
    public class FeignConfig {

    复制代码
     /**
      * 日志级别
      * @return
      */
     @Bean
     public Logger.Level feignLoggerLevel(){
         return Logger.Level.FULL;
     }

    }

    1. 在 application.yml 中配置 Client 的日志级别才能正常输出日志

格式:logging.level.feign 接口包路径=debug

复制代码
logging:
  level:
    com.icoolkj.mall.user.openfeign.demo.feign: debug
    1. 重启测试

分别选择日志级别进行测试,查看控制台输出结果信息。

局部生效:让指定的微服务生效,在 @FeignClient 注解中指定 configuration

复制代码
@FeignClient(name = "icoolkj-mall-order01", path = "/api/order", configuration = OpenFeignConfig.class)
public interface OrderFeignService {

注意:此时配置类不能添加 @Configuration 注解 。

3.2.1.1.2. yum 配置文件方式

全局生效:配置{服务名} 为 default,对应的微服务调用者都生效。

复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            loggerLevel: FULL

局部生效:配置{服务名}为具体的服务名(icoolkj-mall-order01),对调用的微服务提供者生效。

复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          icoolkj-mall-order01:
            loggerLevel: FULL
3.2.1.1.3. 配置方式选择

建议使用 yml 配置,可以利用配置中心对配置进行统一管理。

3.3. 超时时间配置

3.3.1.1. OpenFeign 使用两个超时参数

connectionTimeout 可以防止由于较长的服务器处理时间而阻塞调用者。

readTimeout 从连接建立时开始应用,当返回响应花费太长时间时触发。

注意:OpenFeign 底层使用 LoadBalancer,但是超时以 OpenFeign 配置为准。

3.3.1.2. 配置方式
3.3.1.2.1. Java bean 方式

通过 Options 可以配置连接超时时间和读取超时时间,Options 的第一个参数是连接的超时时间(ms);第二个是请求处理的超时时间(ms)。

复制代码
@Bean
public Request.Options options() {
    return new Request.Options(3000, 5000);
}
3.3.1.2.2. yml 配置文件方式
复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          icoolkj-mall-order01:
            loggerLevel: FULL
            # 连接超时时间
            connectTimeout: 3000
            # 请求处理超时时间
            readTimeout: 5000
3.3.1.2.3. 测试配置是否生效

利用 Thread.sleep 来修改订单接口调用时间,验证是否超时。

3.4. 契约配置

Spring Cloud 在 Feign 的基础上做了扩展,可以让 Feign 支持 Spring MVC 的注解来调用。原生的 Feign 是不支持 Spring MVC 注解的,如果你想在 Spring Cloud 中使用原生的注解方式来定义客户端也是可以的,通过配置契约来改变这个配置,Spring Cloud 中默认的是 SpringMvcContract。

3.4.1.1. 配置方式
3.4.1.1.1. Java Bean 方式
复制代码
@Bean
public Contract feignContract(){
    return new Contract.Default();
}
3.4.1.1.2. yml 配置文件方式
复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          icoolkj-mall-order01:
            loggerLevel: FULL
            # 连接超时时间
            connectTimeout: 3000
            # 请求处理超时时间
            readTimeout: 5000
            # 指定
            contract: feign.Contract.Default

注意:修改契约配置后,OrderFeignService 不在支持 SpringMvc 的注解,需要使用 Feign 原生的注解。

Class OrderFeignService has annotations [FeignClient] that are not used by contract Default

复制代码
@FeignClient(name = "icoolkj-mall-order01", path = "/api/order")
public interface OrderFeignService {
    // 使用 Feign 原生注解调用
    @RequestLine("GET /getOrderByUserId?userId={userId}")
    Result<List<OrderResponse>> getOrderByUserId(@Param("userId") Long userId);
}

3.5. 客户端组件配置

Feign 中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,没有连接池,我们可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient5,OKHttp。

Feign 发起调用真正执行逻辑:feign.Client#execute(扩展点)

3.5.1.1. 配置 Apache HttpClient5(推荐)
3.5.1.1.1. 引入依赖
复制代码
<!-- Apache HttpClient5 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-hc5</artifactId>
</dependency>
3.5.1.1.2. 修改 yml 配置,启用 Apache HttpClient5,引入依赖后默认启用的,可以忽略
复制代码
spring:
  cloud:
    openfeign:
      httpclient:
        hc5:
          enabled: true

关于配置可参考:org.springframework.cloud.openfeign.FeignAutoConfiguration

3.5.1.1.3. 重启测试

调用会进入 feign.hc5.ApacheHttp5Client#execute

3.5.1.2. 配置 OkHttp
3.5.1.2.1. 引入依赖
复制代码
<!-- okhttp -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>
3.5.1.2.2. 修改 yml 配置,将 Feign 的 HttpClient 禁用,启用 OkHttp。
复制代码
spring:
  cloud:
    openfeign:
      httpclient:
        hc5:
          enabled: false
      okhttp:
        enabled: true

关于配置可参考:org.springframework.cloud.openfeign.FeignAutoConfiguration

3.5.1.2.3. 重启测试

调用会进入 feign.okhttp.OkHttpClient#execute

3.6. GZIP 压缩配置

开启压缩可以有效节约网络资源,提升接口性能,我们可以配置 GZIP 来压缩数据

复制代码
spring:
    openfeign:
      compression: # 配置 GZIP 来压缩数据
        request:
          enabled: true
          mime-types: text/xml,application/xml,application/json
          min-request-size: 1024 # 最小请求压缩阈值
        response:
          enabled: true

关于配置可参考:

org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration

3.7. 编码器解码器配置

Feign 中提供了自定义的编码器解码器设置,同时也提供了多种编码器的实现,比如 Gson、Jaxb、Jackson。我们可以用不同的编码器解码器来处理数据的传输。如果你想传输 XML 格式的数据,可以自定义 XML 编码器解码器来实现,或者使用官方提供的 Jaxb。

扩展点:feign.codec.Encoder & feign.codec.Decoder

复制代码
public interface Encoder {
  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
}

public interface Decoder {
    Object decode(Response var1, Type var2) throws IOException, DecodeException, FeignException;
}

3.8. 配置方式

3.8.1.1.1. 引入依赖
复制代码
<!-- jackson -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
</dependency>
3.8.1.1.2. Java Bean 方式
复制代码
@Bean
public Encoder encoder(){
    return new JacksonEncoder();
}

@Bean
public Decoder decoder(){
    return new JacksonDecoder();
}
3.8.1.1.3. yml 配置文件方式
复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          icoolkj-mall-order01:
            # 配置编码器解码器
            encoder: feign.jackson.JacksonEncoder
            decoder: feign.jackson.JacksonDecoder

3.9. 拦截器配置

通过拦截器实现参数传递。

常用场景:统一添加 header 信息,比如向服务提供者传递全局事务 XID,会员 ID,认证 token 令牌,链路追踪的 traceID 等等。

扩展点:feign.RequestInterceptor

复制代码
public interface RequestInterceptor {
    void apply(RequestTemplate var1);
}

每次 feign 发起 http 调用之前,会去执行拦截器中的逻辑。

3.9.1. 自定义拦截器实现认证逻辑

需求场景,微服务调用链路需要传递请求头的 token 信息

如果不做任何配置,直接使用 openFeign 在服务间进行调用就会丢失请求头。

解决方案:

3.9.1.1. 方案1:增加接口参数
复制代码
@RequestMapping(value = "/api/product/getPriceProduct", method = RequestMethod.GET)  
String getPriceProduct(@RequestParam(value = "productId") Long productId, @RequestHeader(value = "token") String token);

该方案不好,代码有侵入性,需要开发人员每次手动获取和添加接口参数。

3.9.1.2. 方案2:添加拦截器

OpenFeign 在远程调用之前会遍历容器中的 RequestInterceptor,调用 RequestInterceptor 的 apply 方法,创建一个新的 Request 进行远程服务调用。因此可以通过实现 RequestInterceptor 给容器中添加自定义的 RequestInterceptor 实现类,这个类里面设置需要发送请求的参数,比如请求头信息,链路追踪信息等。

3.9.1.2.1. 代码实现拦截器:
复制代码
@Slf4j
public class FeignAuthRequestInterceptor implements RequestInterceptor{

    @Override
    public void apply(RequestTemplate template) {
        // 业务逻辑,模拟认证逻辑
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        if(null != requestAttributes){
            HttpServletRequest request = requestAttributes.getRequest();
            String access_token = request.getHeader("Authorization");
            log.info("从 Request 中解析请求头:{}", access_token);
            // 设置 token
            template.header("Authorization", access_token);
        }
    }
}
3.9.1.2.2. 配置拦截器生效

方式1,Java Bean

复制代码
@Bean
public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){
    return new FeignAuthRequestInterceptor();
}

方式2,yml 配置文件

复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          icoolkj-mall-order01:  #对应微服务
            request-interceptors:   # 配置拦截器
              - com.icoolkj.mall.user.openfeign.demo.interceptor.FeignAuthRequestInterceptor
3.9.1.2.3. 重启测试

postman 中增加请求头参数 Authorization,查看会员服务 openFeign 日志是否有 Authorization 信息。

相关推荐
稻草人22222 小时前
java Excel 导出 ,如何实现八倍效率优化,以及代码分层,方法封装
后端·架构
数据智能老司机4 小时前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机5 小时前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
bobz9658 小时前
k8s svc 实现的技术演化:iptables --> ipvs --> cilium
架构
云舟吖8 小时前
基于 electron-vite 实现一个 RPA 网页自动化工具
前端·架构
brzhang10 小时前
当AI接管80%的执行,你“不可替代”的价值,藏在这20%里
前端·后端·架构
Lei活在当下21 小时前
【业务场景架构实战】4. 支付状态分层流转的设计和实现
架构·android jetpack·响应式设计
架构师沉默1 天前
设计多租户 SaaS 系统,如何做到数据隔离 & 资源配额?
java·后端·架构
kfyty7251 天前
不依赖第三方,不销毁重建,loveqq 框架如何原生实现动态线程池?
java·架构
刘立军1 天前
本地大模型编程实战(33)用SSE实现大模型的流式输出
架构·langchain·全栈