跳至主要內容

OpenFeign使用及原理分析

向往大约 5 分钟Spring Cloud

概览

Feign是在RestTemplate基础上封装的,使用注解的方式来声明一组与服务提供者Rest接口所对应的本地Java API接口方法。Feign将远程Rest接口抽象成一个声明式的FeignClient(Java API)客户端,并且负责完成FeignClient客户端和服务提供方的Rest接口绑定。

使用

  1. 添加依赖

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>2.2.10.RELEASE</version>
            </dependency>
    
  2. 在启动类添加注解,指定feign basePackages路径

    @EnableFeignClients(basePackages = "com.xw.order.feign")
    

JDK动态代理机制

动态代理实质是通过java.lang.reflect.Proxy#newProxyInstance方法生成已给动态代理实例,相关参数如下:

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) 

参数说明:

  • 第一个参数为ClassLoader类加载器类型,此处的类加载器和被委托类的类加载器相同即可
  • 第二个参数为Class[]类型,代表动态代理类将会实现的抽象接口,此接口是被委托类所实现的接口。
  • 第三个参数为InvocationHandler类型,它的调用处理器实例将作为JDK生成的动态代理对象的内部成员,在对动态代理对象进行方法调用时,该处理器的invoke(...)方法会被执行。

示例说明:

声明EchoService及其实现类:

public interface EchoService {

    void echo();
}

public class EchoServiceImpl implements EchoService {
    @Override
    public void echo() {
        System.out.println("echo method");
    }
}

编写调用处理器:

public class EchoServiceInvocationHandler implements InvocationHandler {


    private EchoService echoService;

    public EchoServiceInvocationHandler(EchoService echoService) {
        this.echoService = echoService;
    }

    public EchoServiceInvocationHandler() {
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始代理调用");
        echoService.echo();
        return null;
    }
}

编写测试类:

    @Test
    void testJdkProxy() {

        EchoServiceImpl echoServiceImpl = new EchoServiceImpl();
        EchoServiceInvocationHandler echoServiceInvocationHandler = new EchoServiceInvocationHandler(echoServiceImpl);
        EchoService echoService =
                (EchoService) Proxy.newProxyInstance(this.getClass().getClassLoader(), EchoServiceImpl.class.getInterfaces(), echoServiceInvocationHandler);
        echoService.echo();

    }

结果如下:

image-20230208155641144

Feign重要组件

在分析Feign的执行流程之前,我们先对Feign的一些重要的组件进行了解。

Feign的调用处理器InvocationHandler

JDK Proxy生成动态代理核心是 定义一个调用处理器。Fegin提供了一个默认的调用处理器FeignInvocationHandler,当Feign与Hystix组合使用时,将会使用HystrixInvocationHandler,默认的调用处理器FeignInvocationHandler是一个相对简单的类,有一个非常重要的Map类型成员dispatch映射,保存着RPC方法反射实例到Feign的方法处理器MethodHandler实例的映射。key为client的method对象,value为该方法对应的方法处理器实例。

image-20230208161413540

Feign的方法处理器MethodHandler

Feign的MethodHandler接口是Feign自定义接口,是一个非常简单的接口,只有一个invoke方法。内置的SynchronousMethodHandler同步方法处理实现类是Feign的一个重要类,提供了基本的远程URL的同步请求响应处理。调用的核心代码如下:

 @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

客户端Client

客户端组件是Feign中一个非常重要的组件,负责最终的HTTP(包括REST)请求的执行。它的核心逻辑:发送Request请求到服务器,在接收到Response响应后进行解码,并返回结果。

常用的Feign客户端实现类如下:

  • Client.Default类:默认的实现类,使用JDK的HttpURLConnnection类提交HTTP请求。
  • ApacheHttpClient类:该客户端类在内部使用Apache HttpClient开源组件提交HTTP请求。
  • OkHttpClient类:该客户端类在内部使用OkHttp3开源组件提交HTTP请求。
  • LoadBalancerFeignClient类:内部使用Ribbon负载均衡技术完成HTTP请求处理。

建议

默认使用的Client.Default无法做到连接复用,请勿在生产环境使用。

Feign流程分析

  1. 通过应用启动类上的@EnableFeignClients注解开启Feign的装配和远程代理实例创建。在@EnableFeignClients注解源码中可以看到导入了FeignClientsRegistrar类,该类用于扫描@FeignClient注解过的RPC接口。

    image-20230208162453787

  2. 通过对@FeignClient注解RPC接口扫描创建远程调用的动态代理实例。FeignClientsRegistrar类会进行包扫描,扫描所有包下@FeignClient注解过的接口,创建RPC接口的FactoryBean工厂类实例,并将这些FactoryBean注入Spring IOC容器中。如果应用某些地方需要注入RPC接口的实例(比如被@Resource引用),Spring就会通过注册的FactoryBean工厂类实例的getObject()方法获取RPC接口的动态代理实例。在创建RPC接口的动态代理实例时,Feign会为每一个RPC接口创建一个调用处理器,也会为接口的每一个RPC方法创建一个方法处理器,并且将方法处理器缓存在调用处理器的dispatch映射成员中

  3. 发生RPC调用时,eign会根据RPC方法的反射实例从调用处理器的dispatch成员中取得方法处理器,然后由MethodHandler方法处理器开始HTTP请求处理。MethodHandler会结合实际的调用参数,通过RequesTemplate模板实例生成Request请求实例。最后,将Request请求实例交给feign.Client客户端实例进一步完成HTTP请求处理。client有feign.client.Default、ApacheHttpClient、OkHttpClient、LoadBalancerFeignClient等。

参考