Administrator
发布于 2023-05-04 / 0 阅读
0
0

Feign-202509120221

Feign-202509120221

Feign

一、Feign是什么

微服务调用组件Feign的原理及高级功能是我们今天分享的主题,此组件可以说是微服务必

用的,服务远程调用,属于RPC远程调用的一种,RPC 全称是 Remote Procedure Call ,即远程

过程调用,其对应的是我们的本地调用。RPC 的目的是:让我们调用远程方法像调用本地方法一

样。

RPC框架设计架构:

二、Fegin的原理

1、 Ribbon&Feign对比

1.2、Feign进行微服务调用

2、Feign的设计架构

三、Spring Cloud 整合Feign

注意:以下所有步骤实现的前提是需要在启动类上加入注解 @EnableFeignClients 主要是为

开启feign接口扫描

1、Spring Cloud Alibaba整合Feign

1.1、引入核心依赖

1.2 创建服务提供者(provider)

1.2.1  在服务提供者中调用servic层获取数据

//创建服务提供者

@RestController

@RequestMapping("/provider")

public class ProviderController {

1.3 创建feign接口

1.4 创建服务消费者(consumer)

@Autowired

private UserService userService;


@RequestMapping("/getUserById/{id}")

public User getUserById(@PathVariable Integer id){

return userService.getUserById(id);

}

}

/**

  • @FeignClient 一般采用服务名进行命名
  • name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名
  • 称,用于服务发现

  • url: url一般用于调试,可以手动指定@FeignClient调用的地址
  • *

  • @RequestMapping 主要用于feign框架拼接传递url,弥补了Ribbon的url需要手动拼接
  • 的缺陷

  • @PathVariable("id") 当路径为restful风格时路径传参方式
  • @RequestParam("id") 当路径为?id=250 时传参方式
  • @RequestBody User user 当路径为对象时采用的传参方式,(集合、数组等都属于对
  • 象)

    */

    @FeignClient("feign-provider")

    @RequestMapping("/provider")

    public interface UserFeign {


    @RequestMapping("/getUserById/{id}")

    User getUserById(@PathVariable("id") Integer id);


    @RequestMapping("/deleteUserById")

    User deleteUserById(@RequestParam("id") Integer id);//?形式拼接参

    数,?id=250


    @RequestMapping("/addUser")

    User addUser(@RequestBody User user);//pojo--->json

    }

    //服务消费者

    @RestController

    @RequestMapping("/consumer")

    public class ConsumerController {


    四、Feign 原理

    1、将feign接口的代理类扫描到Spring容器中:

    @EnableFeignClients开启feign注解扫描:FeignClientsRegistrar.registerFeignClients()扫描

    被 @FeignClient标识的接口生成代理类,并把接口和代理类交给Spring的容器管理。

    2、为接口的方法创建RequestTemplate

    当consumer调用feign代理类时,代理类会调用SynchronousMethodHandler.invoke()创建Re

    questTemplate(url,参数)

    3、发出请求

    代理类会通过RequestTemplate创建Request,然后client(URLConnetct、HttpClient、OkHtt

    p)使用Request发送请求

    五、Feign 远程调用的重要组件

    在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创

    建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容

    器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。

    为了清晰的介绍SpringCloud中Feign运行机制和原理,在这里,首先为大家梳理一下Feign中几个

    重要组件。

    1. 远程接口的本地JDK Proxy代理实例

    远程接口的本地JDK Proxy代理实例,有以下特点:

    (1)Proxy代理实例,实现了一个加 @FeignClient 注解的远程调用接口;

    (2)Proxy代理实例,能在内部进行HTTP请求的封装,以及发送HTTP 请求;

    (3)Proxy代理实例,能处理远程HTTP请求的响应,并且完成结果的解码,然后返回给调用

    者。

    下面以一个简单的远程服务的调用接口 DemoClient 为例,具体介绍一下远程接口的本地JDK Pro

    xy代理实例的创建过程。

    DemoClient 接口,有两个非常简单的远程调用抽象方法:一个为hello() 抽象方法,用于完成远

    程URL “/api/demo/hello/v1”的HTTP请求;一个为 echo(…) 抽象方法,用于完成远程URL “/api/d

    emo/echo/{word}/v1”的HTTP请求。具体如下图所示。

    @Autowired

    private UserFeign userFeign;


    @RequestMapping("/getUserById/{id}")

    public User getUserById(@PathVariable Integer id){

    return userFeign.getUserById(id);

    }

    }

    DemoClient 接口代码如下:

    package com.crazymaker.springcloud.demo.contract.client;

    //…省略import

    @FeignClient(

    value = "seckill-provider", path = "/api/demo/",

    fallback = DemoDefaultFallback.class)

    public interface DemoClient {

    /**

  • 测试远程调用
  • *

  • @return hello
  • */

    @GetMapping("/hello/v1")

    Result hello();

    /**

  • 非常简单的一个 回显 接口,主要用于远程调用
  • *

  • @return echo 回显消息
  • */

    @RequestMapping(value = "/echo/{word}/v1", method =

    RequestMethod.GET)

    Result echo(

    @PathVariable(value = "word") String word);

    }

    注意,上面的代码中,在DemoClient 接口上,加有@FeignClient 注解。也即是说,Feign在启动

    时,会为其创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。

    如何使用呢?可以通过@Resource注解,按照类型匹配(这里的类型为DemoClient接口类型),

    从Spring IOC容器找到这个代理实例,并且装配给需要的成员变量。

    DemoClient的 本地JDK Proxy 代理实例的使用的代码如下:

    DemoClient的本地JDK Proxy代理实例的创建过程,比较复杂,稍后作为重点介绍。先来看另外

    两个重要的逻辑组件。

    2. 调用处理器 InvocationHandler

    大家知道,通过 JDK Proxy 生成动态代理类,核心步骤就是需要定制一个调用处理器,具体来

    说,就是实现JDK中位于java.lang.reflect 包中的 InvocationHandler 调用处理器接口,并且实现

    该接口的 invoke(…) 抽象方法。

    为了创建Feign的远程接口的代理实现类,Feign提供了自己的一个默认的调用处理器,叫做 Feig

    nInvocationHandler 类,该类处于 feign-core 核心jar包中。当然,调用处理器可以进行替换,如

    果Feign与Hystrix结合使用,则会替换成 HystrixInvocationHandler 调用处理器类,类处于 feign-

    hystrix 的jar包中。

    package com.crazymaker.springcloud.user.info.controller;

    //…省略import

    @Api(value = "用户信息、基础学习DEMO", tags = {"用户信息、基础学习DEMO"})

    @RestController

    @RequestMapping("/api/user")

    public class UserController {

    @Resource

    DemoClient demoClient; //装配 DemoClient 的本地代理实例

    @GetMapping("/say/hello/v1")

    @ApiOperation(value = "测试远程调用速度")

    public Result hello() {

    Result result = demoClient.hello();

    JSONObject data = new JSONObject();

    data.put("others", result);

    return Result.success(data).setMsg("操作成功");

    }

    //…

    }

    图3 Feign中实现的 InvocationHandler 调用处理器

    3. 默认的调用处理器 FeignInvocationHandler

    默认的调用处理器 FeignInvocationHandler 是一个相对简单的类,有一个非常重要Map类型成员

    dispatch 映射,保存着远程接口方法到MethodHandler方法处理器的映射。

    以前面示例中DemoClient 接口为例,其代理实现类的调用处理器 FeignInvocationHandler 的disp

    atch 成员的内存结构图如图3所示。

    图4 DemoClient代理实例的调用处理器 FeignInvocationHandler的dispatch 成员

    为何在图3中的Map类型成员 dispatch 映射对象中,有两个Key-Value键值对呢?

    原因是:默认的调用处理器 FeignInvocationHandle,在处理远程方法调用的时候,会根据Java

    反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理器,然后交给M

    ethodHandler 完成实际的HTTP请求和结果的处理。前面示例中的 DemoClient 远程调用接口,

    有两个远程调用方法,所以,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成

    员,有两个有两个Key-Value键值对。

    FeignInvocationHandler的关键源码,节选如下:

    源码很简单,重点在于invoke(…)方法,虽然核心代码只有一行,但是其功能是复杂的:

    (1)根据Java反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理

    器;

    (2)调用MethodHandler方法处理器的 invoke(...) 方法,完成实际的HTTP请求和结果的处理。

    补充说明一下:MethodHandler 方法处理器,和JDK 动态代理机制中位于 java.lang.reflect 包的 I

    nvocationHandler 调用处理器接口,没有任何的继承和实现关系。MethodHandler 仅仅是Feign

    自定义的,一个非常简单接口。

    package feign;

    //...省略import

    public class ReflectiveFeign extends Feign {

    //...

    //内部类:默认的Feign调用处理器 FeignInvocationHandler

    static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;

    //方法实例对象和方法处理器的映射

    private final Map dispatch;

    //构造函数

    FeignInvocationHandler(Target target, Map

    dispatch) {

    this.target = checkNotNull(target, "target");

    this.dispatch = checkNotNull(dispatch, "dispatch for %s",

    target);

    }

    //默认Feign调用的处理

    @Override

    public Object invoke(Object proxy, Method method, Object[] args)

    throws Throwable {

    //...

    //首先,根据方法实例,从方法实例对象和方法处理器的映射中,

    //取得 方法处理器,然后,调用 方法处理器 的 invoke(...) 方法

    return dispatch.get(method).invoke(args);

    }

    //...

    }


    评论