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("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 {
/**
*
*/
@GetMapping("/hello/v1")
Result
/**
*
*/
@RequestMapping(value = "/echo/{word}/v1", method =
RequestMethod.GET)
Result
@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
Result
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
//构造函数
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);
}
//...
}