您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

关于OpenFeign那点事儿 - 使用篇

时间:2022-03-28 12:36:16  来源:  作者:Anyin

引言

Hello 大家好,这里是Anyin。

在我们微服务开发过程中不可避免的会涉及到微服务之间的调用,例如:认证Auth服务需要去用户User服务获取用户信息。在Spring Cloud全家桶的背景下,我们一般都是使用Feign组件进行服务之间的调用。

关于一般的Feign组件使用相信大家都很熟悉,但是在搭建整个微服务架构的时候Feign组件遇到的问题也都熟悉吗 ? 今天我们来聊一聊。

基础使用

首先,我们先实现一个Feign组件的使用方法。

1.导入包

     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-loadbalancer</artifactId>
     </dependency>


     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-openfeign</artifactId>
     </dependency>

这里也导入了一个Loadbalancer组件,因为在Feign底层还会使用到负载均衡器进行客户端负载。

2.配置启用FeignClient

@EnableFeignClients(basePackages = {
     "org.anyin.gitee.cloud.center.upms.api"
})

在我们的mAIn入口的类上添加上@EnableFeignClients注解,并指定了包扫描的位置

3.编写FeignClient接口

@FeignClient(name = "anyin-center-upms",
     contextId = "SysUserFeignApi",
     configuration = FeignConfig.class,
     path = "/api/sys-user")
public interface SysUserFeignApi {
 @GetMApping("/info/mobile")
 ApiResponse<SysUserResp> infoByMobile(@RequestParam("mobile") String mobile);
}

我们自定义了一个SysUserFeignApi接口,并且添加上了@FeignClient注解。相关属性说明如下:

•name 应用名,其实就是spring.application.name,用于标识某个应用,并且能从注册中心拿到对应的运行实例信息

•contextId 当你多个接口都使用了一样的name值,则需要通过contextId来进行区分

•configuration 指定了具体的配置类

•path 请求的前缀

1.编写FeignClient接口实现

@RestController
@RequestMapping("/api/sys-user")
public class SysUserFeignController implements SysUserFeignApi {
 @Autowired
 private SysUserRepository sysUserRepository;
 @Autowired
 private SysUserConvert sysUserConvert;
 @Override
 public ApiResponse<SysUserResp> infoByMobile(@RequestParam("mobile") String mobile) {
     SysUser user = sysUserRepository.infoByMobile(mobile);
     SysUserResp resp = sysUserConvert.getSysUserResp(user);
     return ApiResponse.success(resp);
 }
}

这个就是一个简单的Controller类和对应的方法,用于根据手机号查询用户信息。

2.客户端使用

@Component
@Slf4j
public class MobileInfoHandler{
 @Autowired
 private SysUserFeignApi sysUserFeignApi;
 @Override
 public SysUserResp info(String mobile) {
     SysUserResp sysUser = sysUserFeignApi.infoByMobile(mobile).getData();
     if(sysUser == null){
         throw AuthExCodeEnum.USER_NOT_REGISTER.getException();
     }
     return sysUser;
 }
}

这个是我们在客户端服务使用Feign组件的代码,它就像一个Service方法一样,直接调用就行。无需处理请求和响应过程中关于参数的转换。

至此,我们的一个Feign组件基本使用的代码就完成了。这个时候我们信心满满地赶紧运行下我们代码,测试下接口是否正常。

以上的代码,是能够正常运行的。但是随着我们遇到场景的增多,我们会发现,理想很丰满,显示很骨感,以上的代码并不能100%适应我们遇到的场景。

接下来,我们来看看我们遇到哪些场景以及这些场景需要怎么解决。

场景一:日志

在以上的代码中,因为我们未做任何配置,所以sysUserFeignApi.infoByMobile方法对于我们来讲就像一个黑盒。

虽然我们传递了mobile值,但是不知道真实请求用户服务的值是什么,是否有其他信息一起传递?虽然方法返回的参数是SysUserResp实体,但是我们不知道用户服务返回的是什么,是否有其他信息一起返回?虽然我们知道Feign组件底层是http实现,那么请求的过程是否有传递header信息?

这一切对我们来讲就是一个黑盒,极大阻碍我们拔刀(排查问题)的速度。所以,我们需要配置日志,用于显示请求过程中的所有信息传递。

在刚@FeignClient注解有个参数,configuration 指定了具体的配置类,我们可以在这里指定日志的级别。如下:

public class FeignConfig {
    @Bean
    public Logger.Level loggerLevel(){
        return Logger.Level.FULL;
    }
}

接着还需要在配置文件指定具体FeignClient的日志级别为DEBUG。

logging:
  level:
    root: info
    org.anyin.gitee.cloud.center.upms.api.SysUserFeignApi: debug

这个时候,你在请求接口的时候,会发现多了好多日志。

关于OpenFeign那点事儿 - 使用篇

 

这里就可以详细看到,在请求开始的时候携带的所有header信息以及请求参数信息,在响应回来的时候通用打印了所有的响应信息。

场景二:透传header信息

在上一节中,我们在日志中看到了很多的请求头header的信息,这些都是程序自己添加的吗 ? 很明显不是。例如x-application-name和x-request-id,这两个参数就是我们自己添加的。

需要透传header信息的场景,一般是出现在租户ID或者请求ID的场景下。我们这里以请求ID为例,我们知道用户的一个请求,可能会涉及多个服务实例,当程序出现问题的时候为了方便排查,我们一般会使用请求ID来标识一次用户请求,并且这个请求ID贯穿所有经过的服务实例,并且在日志中打印出来。这样子,当出现问题的时候,根据该请求ID就可以捞出本次用户请求的所有日志信息。

关于请求ID打印到日志可以参考:
不会吧,你还不会用RequestId看日志 ?[1]

基于这种场景,我们需要手动设置透传信息,Feign组件也给我们提供了对应的方式。 只要实现了RequestInterceptor接口,即可透传header信息。

public class FeignRequestInterceptor implements RequestInterceptor {
    @Value("${spring.application.name}")
    private String app;
    @Override
    public void apply(RequestTemplate template) {
        HttpServletRequest request = WebContext.getRequest();
        // job 类型的任务,可能没有Request
        if(request != null && request.getHeaderNames() != null){
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                // Accept值不传递,避免出现需要响应xml的情况
                if ("Accept".equalsIgnoreCase(name)) {
                    continue;
                }
                String values = request.getHeader(name);
                template.header(name, values);
            }
        }
        template.header(CommonConstants.APPLICATION_NAME, app);
        template.header(CommonConstants.REQUEST_ID, RequestIdUtil.getRequestId().toString());
        template.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    }
}

场景三:异常处理

在第一节我们客户调用的时候,我们是没有处理异常的,我们直接.getData()直接返回了,这其实是一个非常危险的操作,.getData()的返回结果可能是null,很容易造成NPE的情况。

        SysUserResp sysUser = sysUserFeignApi.infoByMobile(mobile).getData();

回想下,当我们调用其他当前服务的Service方法的时候,如果遇到异常,是不是就是直接抛出异常,交由统一异常处理器进行处理?所以,这里我们也是期望调用Feign和Service一样,遇到异常,交由统一异常进行处理。

如何处理这个需求呢? 我们可以在解码的时候进行处理。

@Slf4j
public class FeignDecoder implements Decoder {
    // 代理默认的解码器
    private Decoder decoder;
    public FeignDecoder(Decoder decoder) {
        this.decoder = decoder;
    }
    @Override
    public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
        // 序列化为json
        String json = this.getResponseJson(response);
        this.processCommonException(json);
        return decoder.decode(response, type);
    }
    // 处理公共业务异常
    private void processCommonException(String json){
        if(!StringUtils.hasLength(json)){
            return;
        }
        ApiResponse resp = JSONUtil.toBean(json, ApiResponse.class);
        if(resp.getSuccess()){
            return;
        }
        log.info("feign response error: code={}, message={}", resp.getCode(), resp.getMessage());
        // 抛出我们期望的业务异常
        throw new CommonException(resp.getCode(), resp.getMessage());
    }
    // 响应值转json字符串
    private String getResponseJson(Response response) throws IOException {
        try (InputStream inputStream = response.body().asInputStream()) {
            return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        }
    }
}

这里我们的处理方式是在解码的时候,先从响应结果中拿到是否有业务异常的判断,如果有,则构造业务异常实例,然后抛出信息。

运行下代码,我们会发现统一异常那边还是无法处理由下游服务返回的异常,原因是虽然我们抛出了一个CommonException,但是其实最后还是会被Feign捕获,然后重新封装为DecodeException异常,再进行抛出

Object decode(Response response, Type type) throws IOException {
    try {
      return decoder.decode(response, type);
    } catch (final FeignException e) {
      throw e;
    } catch (final RuntimeException e) {
      // 重新封装异常
      throw new DecodeException(response.status(), e.getMessage(), response.request(), e);
    }
  }

所以,我们还需要在统一异常那边再做下处理,代码如下:

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(DecodeException.class)
    public ApiResponse decodeException(DecodeException ex){
        log.error("解码失败: {}", ex.getMessage());
        String id = RequestIdUtil.getRequestId().toString();
        if(ex.getCause() instanceof CommonException){
            CommonException ce = (CommonException)ex.getCause();
            return ApiResponse.error(id, ce.getErrorCode(), ce.getErrorMessage());
        }
        return ApiResponse.error(id, CommonExCodeEnum.DATA_PARSE_ERROR.getCode(), ex.getMessage());
    }

在运行下代码,我们就可以看到异常从用户服务->认证服务->网关->前端这么一个流程。

•用户服务抛出的异常

关于OpenFeign那点事儿 - 使用篇

 

•认证服务抛出的异常

关于OpenFeign那点事儿 - 使用篇

 

•前端显示的异常

关于OpenFeign那点事儿 - 使用篇

 

场景四:时区问题

随着业务的变化,我们可能会在请求参数或者响应参数中增加关于Date类型的参数,这个时候你会发现,它的时区不对,少了8个小时。

这个问题其实是Jackson组件带来的,该问题其实也有不同的解法。

1.在每个Date属性添加上@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")2.传参统一转为yyyy-MM-dd HH:mm:ss格式的字符3.统一配置spring.jackson

很明显,第三种解法最合适,我们在配置文件做如下的配置即可。

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

这里需要注意下,我们@FeignClient的配置是自定义配置的FeignConfig类,在自定义配置类中加载了解码器,而解码器依赖的是全局的HttpMessageConverters实例,和SpringMVC依赖的是同一个实例,所以该配置生效。有些场景下会自定义HttpMessageConverters,那么该配置则不生效。

public class FeignConfig {
    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;
    // 自定义解码器
    @Bean
    public Decoder decoder(ObjectProvider<HttpMessageConverterCustomizer> customizers){
        return new FeignDecoder(
                new OptionalDecoder(
                        new ResponseEntityDecoder(
                                new SpringDecoder(messageConverters, customizers))));
    }
}

其他问题

不知道细心的朋友是否有看到在第一节定义SysUserFeignApi接口的时候,我在@FeignClient注解上使用了一个属性:path,并且接口上没有使用@RequestMapping注解。

回想下,之前我们在使用Feign的时候,是不是这么使用的:

@FeignClient(name = "anyin-center-upms",
        contextId = "SysUserFeignApi",
        configuration = FeignConfig.class)
@RequestMapping("/api/sys-user")        
public interface SysUserFeignApi {}

这里不使用这个方式的原因是我当前版本的Spring Cloud OpenFeign已经不支持识别@RequestMapping注解了,它不会在请求的时候加入到请求的前缀,所以即使解决了@RequestMapping注解被SpringMVC识别为Controller类也无法正常运行。

所以,这里使用了path属性。

最后

以上,就是我们在使用OpenFeign组件的时候会遇到的大部分场景,你了解吗 ?

相关源码地址:Anyin Cloud[2]

References

[1] 不会吧,你还不会用RequestId看日志 ?: https://juejin.cn/post/7029880952666980388
[2] Anyin Cloud: https://gitee.com/anyin/anyin-cloud



Tags:OpenFeign   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
全网最详细的OpenFeign讲解
OpenFeign是一个非常有用的工具,它为开发者提供了一种简单而强大的方式来处理远程服务调用。通过使用OpenFeign,开发者可以专注于业务逻辑,而无需花费太多精力在复杂的网络编程...【详细内容】
2023-11-30  Search: OpenFeign  点击:(183)  评论:(0)  加入收藏
SpringCloud OpenFeign整合Ribbon实现负载均衡及源码分析
负载均衡器在分布式网络中扮演着非常重要的角色。通过负载均衡,可以实现更好的性能和可靠性,同时提高系统的可扩展性和弹性。目前,SpringCloud体系中,主要使用的有两种:Netflix的...【详细内容】
2023-11-09  Search: OpenFeign  点击:(241)  评论:(0)  加入收藏
Spring Cloud 远程调用 OpenFeign 这些知识点,能颠覆你的认知!
环境:SpringBoot2.7.12 + Spring Cloud2021.0.71. 概述Spring Cloud Openfeign是一种声明式、模板化的HTTP客户端,主要用于在Spring Cloud微服务架构中进行服务调用。相比于传...【详细内容】
2023-10-13  Search: OpenFeign  点击:(316)  评论:(0)  加入收藏
使用OpenFeign实现服务调用
OpenFeignOpenFeign是运行在客户端的声明式服务调用的框架,通过声明接口的方式来达到对服务的调用,表面上看起来就好像在调用本地方法一样。OpenFeign使用方法创建一个Springb...【详细内容】
2023-06-12  Search: OpenFeign  点击:(400)  评论:(0)  加入收藏
Spring Boot+Nacos+gRPC,一个区别于 OpenFeign 的微服务通信方案!
gRPC 的基础知识前面跟小伙伴们分享了很多了,今天再写一篇给这个小小的系列收个尾。我们前面介绍的都是 gRPC 的基本用法,最终目的当然是希望能够在 Spring Boot 中用上这个...【详细内容】
2023-04-04  Search: OpenFeign  点击:(233)  评论:(0)  加入收藏
SpringCloud OpenFeign 服务调用传递 token
业务场景通常微服务对于用户认证信息解析有两种方案 在 gateway 就解析用户的 token 然后路由的时候把 userId 等相关信息添加到 header 中传递下去。 在 gateway 直接把 to...【详细内容】
2022-07-24  Search: OpenFeign  点击:(331)  评论:(0)  加入收藏
SpringCloud微服务之OpenFeign添加traceId全链路监控
注册中心请参考: https://blog.csdn.net/MadLifeBin/article/details/120332483可搭建单机版用于 Demo 测试服务提供与消费请参考: https://blog.csdn.net/MadLifeBin/article...【详细内容】
2022-04-26  Search: OpenFeign  点击:(1296)  评论:(0)  加入收藏
关于OpenFeign那点事儿 - 使用篇
引言Hello 大家好,这里是Anyin。在我们微服务开发过程中不可避免的会涉及到微服务之间的调用,例如:认证Auth服务需要去用户User服务获取用户信息。在Spring Cloud全家桶的背景...【详细内容】
2022-03-28  Search: OpenFeign  点击:(1473)  评论:(0)  加入收藏
springcloud 整合openFeign
使用Feign可以完成服务间调用,但是总存在一种情况:服务提供方没有注册到注册中心、服务提供方还没开发完成(因此也就无法调用)等等。此时如果我们需要完成服务之间调用该如何...【详细内容】
2022-03-03  Search: OpenFeign  点击:(446)  评论:(0)  加入收藏
SpringCloud Alibaba实战(使用OpenFeign服务调用)
1、Feign简介Feign是一种声明式、模板化的HTTP客户端。使用Feign,可以做到声明式调用。尽管Feign目前已经不再迭代,处于维护状态,但是Feign仍然是目前使用最广泛的远程调用框架...【详细内容】
2021-06-29  Search: OpenFeign  点击:(484)  评论:(0)  加入收藏
▌简易百科推荐
Qt与Flutter:在跨平台UI框架中哪个更受欢迎?
在跨平台UI框架领域,Qt和Flutter是两个备受瞩目的选择。它们各自具有独特的优势,也各自有着广泛的应用场景。本文将对Qt和Flutter进行详细的比较,以探讨在跨平台UI框架中哪个更...【详细内容】
2024-04-12  刘长伟    Tags:UI框架   点击:(7)  评论:(0)  加入收藏
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(11)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(22)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(60)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(51)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(41)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(57)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(71)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(92)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(90)  评论:(0)  加入收藏
站内最新
站内热门
站内头条