2021-02-18实习日报

1. SpringCloud中Feign原理

1.1 Feign远程调用的基本流程

Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者,Feign远程调用的基本流程

在这里插入图片描述

可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数在应用到请求上,进而转换为真正的Request请求,通过Feign以及JAVA的动态代理机制,使得JAVA的开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用

1.2Feign远程调用的重要组件

在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例,然后将这些本地Proxy代理实例,注入到Spring IOC容器中,当远程接口的方法被调用,由proxy代理实例去完成真正的远程访问,并且返回结果

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

远程接口的本地JDK Proxy代理实例,有以下特点
(1)Proxy代理实例,实现了一个加@FeignClient注解的远程调用接口
(2)Proxy代理实例,能在内部进行HTTP请求的封装,以及发送HTTP请求
(3)Proxy代理实例,能处理远程HTTP请求的响应,并且完成结果的解码,然后返回给调用者
以一个简单的远程服务的调用接口DemoClient为例,具体介绍一下远程接口的本地JDK Proxy代理实例的创建过程

DemoClient接口,有两个非常简单的远程调用抽象方法:一个为hello()抽象方法,用于完成远程URL“/api/demo/hello/v1”的HTTP请求,一个为echo抽象方法,用于完成远程URL“/api/demo/echo/{word}/v1”的HTTP请求
在这里插入图片描述

远程接口的本地JDK Proxy代理实例示意图

@FeignClient(
        value = "seckill-provider", path = "/api/demo/",
        fallback = DemoDefaultFallback.class)
public interface DemoClient {

    /**
     * 测试远程调用
     *
     * @return hello
     */
    @GetMapping("/hello/v1")
    Result<JSONObject> hello();


    /**
     * 非常简单的一个 回显 接口,主要用于远程调用
     *
     * @return echo 回显消息
     */
    @RequestMapping(value = "/echo/{word}/v1", method = RequestMethod.GET)
    Result<JSONObject> echo(
            @PathVariable(value = "word") String word);

}

代码中,在DemoClient接口上加有@FeignClient注解,也就是说Feign在启动时,会为其创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器
可以通过@Resource注解,按照类型匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给需要的成员变量。

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

@Api(value = "用户信息、基础学习DEMO", tags = {"用户信息、基础学习DEMO"})
@RestController
@RequestMapping("/api/user")
public class UserController {

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

    @GetMapping("/say/hello/v1")
    @ApiOperation(value = "测试远程调用速度")
    public Result<JSONObject> hello() {
        Result<JSONObject> result = demoClient.hello();
        JSONObject data = new JSONObject();
        data.put("others", result);
        return Result.success(data).setMsg("操作成功");
    }
//…
}
1.2.2 调用处理器InvocationHandler

通过JDK Proxy生成动态代理类,核心步骤就是需要定制一个调用处理器,就是实现JDK中位于java.lang.reflect包中的InvocationHandler调用处理器接口,并且实现该接口的invoke方法

为了创建Feign的远程接口的代理类,Feign提供了自己的一个默认的调用处理器,叫做FeignInvocationHandler类,该类处于feign-core核心jar包中,当然,调用处理器可以替换,如果Feign与Hystrix结合使用,则会替换成HystrixINvocationHandler调用处理器类,类处于 feign-hystrix 的jar包中
在这里插入图片描述

1.2.3 默认的调用处理器FeignInvocationHandler

默认的调用处理器FeignInvocationHandler是一个相对简单的类,有一个非常重要的Map类型成员dispatch映射,保存着远程接口方法到MethodHandler方法处理器的映射
前面示例中DemoClient 接口为例,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员的内存结构图
在这里插入图片描述

为什么Map类型成员dispatch映射对象中,有两个Key-Value键值对呢
默认的调用处理器FeignInvocationHandler,在处理远程方法调用的时候,会根据java反射的方法实例,在dispatch映射对象中,找到对应的MethodHandler方法处理器,然后交给MethodHandler完成实际的HTTP请求和结果的映射,前面示例中的 DemoClient 远程调用接口,有两个远程调用方法,所以,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员,有两个有两个Key-Value键值对。

FeignInvocationHandler关键源码

package feign;
//...省略import

public class ReflectiveFeign extends Feign {

  //...

  //内部类:默认的Feign调用处理器 FeignInvocationHandler
  static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    //方法实例对象和方法处理器的映射
    private final Map<Method, MethodHandler> dispatch;

    //构造函数    
    FeignInvocationHandler(Target target, Map<Method, MethodHandler> 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);
    }
    //...
  }

代码的功能
(1)根据Java反射的方法实例,在dispatch映射对象中,找到对应的MethodHandler方法处理器
(2)调用MethodHandler方法处理器的invoke方法,完成实际的HTTP请求和结果的处理

1.2.4 方法处理器MethodHandler

Feign的方法处理器MethodHandler是一个独立的接口,定义在InvocationHandlerFactory接口中,仅仅拥有一个invoke()方法,源码如下

//定义在InvocationHandlerFactory接口中
public interface InvocationHandlerFactory {
  //…

 //方法处理器接口,仅仅拥有一个invoke(…)方法
  interface MethodHandler {
    //完成远程URL请求
    Object invoke(Object[] argv) throws Throwable;
  }
//...
}

MethodHandler的invoke方法,主要职责是完成实际远程URL请求,然后返回解码后的远程URL的响应结果,Feign提供了默认的SynchronousMethodHandler实现类,提供了基本的远程URL的同步请求处理
在这里插入图片描述

为了彻底了解方法处理器,来读一下SyncronousMethodHandler方法处理器的源码

package feign;
//…..省略import
final class SynchronousMethodHandler implements MethodHandler {
  //…
  // 执行Handler 的处理
public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate requestTemplate = this.buildTemplateFromArgs.create(argv);
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
                return this.executeAndDecode(requestTemplate);
            } catch (RetryableException var5) {
               //…省略不相干代码
            }
        }
}

  //执行请求,然后解码结果
Object executeAndDecode(RequestTemplate template) throws Throwable {
        Request request = this.targetRequest(template);
        long start = System.nanoTime();
        Response response;
        try {
            response = this.client.execute(request, this.options);
            response.toBuilder().request(request).build();
        }
}
}

SynchronousMethodHandler的invoke方法,调用了自己的executeAndDecode请求执行和结果解码方法,工作步骤
(1)首先通过RequestTemplate请求模板实例,生成远程URL请求实例request
(2)然后用自己的feign客户端client成员,excecute()执行请求,并且获取response响应
(3)对Response响应进行结果编码

1.2.5 Feign客户端组建feign.Client

客户端组件是Feign中一个非常重要的组件,负责端到端的执行URL请求,其核心的逻辑:发送request请求到服务器,并接收response响应后进行解码

feign.client类,是客户端的顶层接口,只有一个抽象方法

package feign;

/**客户端接口
 * Submits HTTP {@link Request requests}. 
Implementations are expected to be thread-safe.
 */
public interface Client {
  //提交HTTP请求,并且接收response响应后进行解码
  Response execute(Request request, Options options) throws IOException;

}

由于不同的feign.client实现类,内部完成HTTP请求的组件和技术不同,故,feign.client有多个不同的实现

(1)Client.Default类:默认的feign.Client 客户端实现类,内部使用HttpURLConnnection 完成URL请求处理;
(2)ApacheHttpClient 类:内部使用 Apache httpclient 开源组件完成URL请求处理的feign.Client 客户端实现类;
(3)OkHttpClient类:内部使用 OkHttp3 开源组件完成URL请求处理的feign.Client 客户端实现类。
(4)LoadBalancerFeignClient 类:内部使用 Ribben 负载均衡技术完成URL请求处理的feign.Client 客户端实现类。
在这里插入图片描述

1.3 Feign客户端实现类
1.3.1 Client.Default类

作为默认的Client接口的实现类,在Client.Default内部使用JDK自带的HTTPURLConnection类实现URL网络请求
在这里插入图片描述

在JKD1.8中,虽然在HttpURLConnnection 底层,使用了非常简单的HTTP连接池技术,但是,其HTTP连接的复用能力,实际是非常弱的,性能当然也很低

1.3.2 ApacheHttpClient类

ApacheHttpClient 客户端类的内部,使用 Apache HttpClient开源组件完成URL请求的处理。

从代码开发的角度而言,Apache HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅使客户端发送Http请求变得容易,而且也方便开发人员测试接口。既提高了开发的效率,也方便提高代码的健壮性。

从性能的角度而言,Apache HttpClient带有连接池的功能,具备优秀的HTTP连接的复用能力。关于带有连接池Apache HttpClient的性能提升倍数,具体可以参见后面的对比试验。

ApacheHttpClient 类处于 feign-httpclient 的专门jar包中,如果使用,还需要通过Maven依赖或者其他的方式,倒入配套版本的专门jar包。

1.3.3 OkHttpClient类

OkHttpClient 客户端类的内部,使用OkHttp3 开源组件完成URL请求处理。OkHttp3 开源组件由Square公司开发,用于替代HttpUrlConnection和Apache HttpClient。由于OkHttp3较好的支持 SPDY协议(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。),从Android4.4开始,google已经开始将Android源码中的 HttpURLConnection 请求类使用OkHttp进行了替换。也就是说,对于Android 移动端APP开发来说,OkHttp3 组件,是基础的开发组件之一。

1.3.4 LoadBalanceFeignClient类

LoadBalancerFeignClient 内部使用了 Ribben 客户端负载均衡技术完成URL请求处理。在原理上,简单的使用了delegate包装代理模式:Ribben负载均衡组件计算出合适的服务端server之后,由内部包装 delegate 代理客户端完成到服务端server的HTTP请求;所封装的 delegate 客户端代理实例的类型,可以是 Client.Default 默认客户端,也可以是 ApacheHttpClient 客户端类或OkHttpClient 高性能客户端类,还可以其他的定制的feign.Client 客户端实现类型。
在这里插入图片描述

1.4 Feign远程调用执行流程

整体流程大致分为四步

第一步 通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。

Feign在启动时,会为加上了@FeignClient注解的所有远程接口(包括 DemoClient 接口),创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。在这里,暂且将这个Proxy代理实例,叫做 DemoClientProxy,稍后,会详细介绍这个Proxy代理实例的具体创建过程。

然后,在本实例的UserController 调用代码中,通过@Resource注解,按照类型或者名称进行匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给@Resource注解所在的成员变量,本实例的成员变量的名称为 demoClient。

在需要代进行hello()远程调用时,直接通过 demoClient 成员变量,调用JDK Proxy动态代理实例的hello()方法。

第二步 执行 InvokeHandler 调用处理器的invoke(…)方法

JDK Proxy动态代理实例的真正的方法调用过程,具体是通过 InvokeHandler 调用处理器完成的。故,这里的DemoClientProxy代理实例,会调用到默认的FeignInvocationHandler 调用处理器实例的invoke(…)方法。

通过前面 FeignInvocationHandler 调用处理器的详细介绍,大家已经知道,默认的调用处理器 FeignInvocationHandle,内部保持了一个远程调用方法实例和方法处理器的一个Key-Value键值对Map映射。FeignInvocationHandle 在其invoke(…)方法中,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的 MethodHandler 方法处理器,然后由后者完成实际的HTTP请求和结果的处理。

所以在第2步中,FeignInvocationHandle 会从自己的 dispatch映射中,找到hello()方法所对应的MethodHandler 方法处理器,然后调用其 invoke(…)方法。

第三步 执行 MethodHandler 方法处理器的invoke(…)方法

通过前面关于 MethodHandler 方法处理器的非常详细的组件介绍,大家都知道,feign默认的方法处理器为 SynchronousMethodHandler,其invoke(…)方法主要是通过内部成员feign客户端成员 client,完成远程 URL 请求执行和获取远程结果。

feign.Client 客户端有多种类型,不同的类型,完成URL请求处理的具体方式不同。

第四步 通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果

如果MethodHandler方法处理器实例中的client客户端,是默认的 feign.Client.Default 实现类性,则使用JDK自带的HttpURLConnnection类,完成远程 URL 请求执行和获取远程结果。

如果MethodHandler方法处理器实例中的client客户端,是 ApacheHttpClient 客户端实现类性,则使用 Apache httpclient 开源组件,完成远程 URL 请求执行和获取远程结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值