关于feign接口动态代理源码解析

 更新时间:2022年03月09日 08:55:50   作者:keygod1  
这篇文章主要介绍了关于feign接口动态代理源码解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

feign接口动态代理源码解析

@FeignClinet 代理类注册

@FeignClinet 通过动态代理实现的底层http调用,既然是动态代理,必然存在创建代理类的过程。如Proxy.newProxyInstance或者 CGlib org.springframework.cloud.openfeign 的代理类注册实现如下。

首先,org.springframework.cloud.openfeign.FeignClientsRegistrar 注册FeignClientFactoryBean到Singleton缓存中. 一个接口对应FeignClientFactoryBean。

spring 初始化容器过程中执行

org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject()
@Override
   public Object getObject() throws Exception {
       return getTarget();
   }
   /**
    * @param <T> the target type of the Feign client
    * @return a {@link Feign} client created with the specified data and the context information
    */
   <T> T getTarget() {
       FeignContext context = applicationContext.getBean(FeignContext.class);
       Feign.Builder builder = feign(context);
       if (!StringUtils.hasText(this.url)) {
           if (!this.name.startsWith("http")) {
               url = "http://" + this.name;
           }
           else {
               url = this.name;
           }
           url += cleanPath();
           return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                   this.name, url));
       }
       if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
           this.url = "http://" + this.url;
       }
       String url = this.url + cleanPath();
       Client client = getOptional(context, Client.class);
       if (client != null) {
           if (client instanceof LoadBalancerFeignClient) {
               // not load balancing because we have a url,
               // but ribbon is on the classpath, so unwrap
               client = ((LoadBalancerFeignClient)client).getDelegate();
           }
           builder.client(client);
       }
       Targeter targeter = get(context, Targeter.class);
       return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
               this.type, this.name, url));
   }

其中 getObject() 实现了 FactoryBean 的 getObject(),

作用是在springContext初始化时创建Bean实例,如果isSingleton()返回true,则该实例会放到Spring容器的单实例缓存池中。

然后是targeter.target() 如果启用了Hystrix调用的就是

org.springframework.cloud.openfeign.HystrixTargeter.target()

org.springframework.cloud.openfeign.HystrixTargeter

/**
* @param factory bean工厂
* @param feign  feign对象的构造类
* @param context feign接口上下文,
* @param target 保存了feign接口的name,url和FeignClient的Class对象
*
**/
@Override
   public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                       Target.HardCodedTarget<T> target) {
       if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
           return feign.target(target);
       }
       feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
       SetterFactory setterFactory = getOptional(factory.getName(), context,
           SetterFactory.class);
       if (setterFactory != null) {
           builder.setterFactory(setterFactory);
       }
       Class<?> fallback = factory.getFallback();
       if (fallback != void.class) {
           return targetWithFallback(factory.getName(), context, target, builder, fallback);
       }
       Class<?> fallbackFactory = factory.getFallbackFactory();
       if (fallbackFactory != void.class) {
           return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
       }
       return feign.target(target);
   }

再看下去 feign.target(target)

feign.Feign.Builder

    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }
    public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }

build() 返回一个ReflectiveFeign对象。

往下看,ReflectiveFeign的newInstance方法。

feign.ReflectiveFeign

@Override
  public <T> T newInstance(Target<T> target) {
    //关键方法: 解析target对象,返回key 为 feign接口的url ,value 为请求执行类:SynchronousMethodHandler
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    //创建代理类 handler ,返回对象  feign.ReflectiveFeign.FeignInvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

至此,代理类注册完成。

当调用feign接口时,其实执行的是 feign.ReflectiveFeign.FeignInvocationHandler的invoke 方法

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }
        //dispatch.get(method)返回的是 SynchronousMethodHandler 对象
      return dispatch.get(method).invoke(args);
    }

调用的 SynchronousMethodHandler invoke 方法。

feign.SynchronousMethodHandler

 @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } 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;
      }
    }
  }

executeAndDecode 方法执行RPC调用的逻辑。

小结一下:FeignClientsRegistrar 解析@FeignClient注解,注册对应的FeignClientFactoryBean–》通过FeignClientFactoryBean的getObject()方法返回代理对象 feign.ReflectiveFeign.FeignInvocationHandler

feign源码解析

首先我要说的是springcloud没有rpc,这就涉及rpc和微服务的区别。springcloud的模块通信工具feign跟httpclient和okhttp是一样的东西,都是对http请求封装的工具,其实feign可以选择httpclient或者okhttp作为底层实现(修改配置即可)。

Feign的作用

①封装http请求,使开发人员对发送请求的过程无感知,给人一种伪rpc感觉(这也许是feign这个名字的由来吧,伪装~)。

②feign整合ribbon和hystrix,结合eureka起到负载均衡和熔断器、降级作用。

源码及流程介绍

我们从@EnableFeignClients这个注解开始追踪

我们发现有个@Import注解,引用FeignClientRegistrar类,跟进去看看

2个方法:①redisterDefalterConfiguration是加载配置,②registerFeignClients扫描你填写的basepackage下的所有@FeignClient注解的接口。第一个方法没啥好说的,我们主要看看第二个方法。

扫描完之后,把所有包含@FeignClient注解的接口都注册到spring的beanfactory去,让开发人员可以@Autowired来调用。这一部分代码我就不贴了,我们只是追求feign的原理流程,太涉及spring源码部分,我不做解释。

=========== 以上是feign注册流程,下面介绍拼装request请求部分 ===========

首先,这里看ReflectiveFeign类,这个类用的是jdk的动态代理

用到代理模式肯定是在发送feign请求之前做一些操作,继续看看请求之前做了哪些操作。

代理拦截每一个FeignClient请求,进入SynchronousMethodHandler的invoke方法,该方法调用executeAndDecode方法,这个方法看名字就知道是创建请求的方法,进去看看。

在该方法发送请求并且解码,解码分为decoder和errordecoder,这两个都是可以重写。这里你可能会问解码器,那编码器呢,feign默认用springEncoder,同样是可以替换成Gson等。

=========== 以上是feign的调用流程,以下是feign使用过程的坑 ===========

①feign在D版本后默认关闭hystrix,要想传递请求头,如果不用hystrix的话在feign拦截器里塞一遍就好;如果要用hystrix,那么改用信号量。

②在C版本后默认关闭hystrix,要使用要手动开启

③不要妄想改变feign的逻辑,因为代理模式被写成final,无法修改

④无法在解码器里抛自定义异常,因为feign最终会统一拦截,抛出一个feignexception。你想把统一拦截也改了,那么你可以看看第③坑。

⑤feign的重试机制,默认是1,也就是说超时时间会变成2倍。这个可以通过配置修改。

⑥feign集成的负载均衡器ribbon,feign有个缓存,ribbon也有个缓存,会造成上线延迟,可以修改配置实现。

⑦feign对格式化时间处理有问题

⑧如果你是使用生产者提供api,并且实现该接口,@requestparam可以不用在实现类写,但是@requestbody不写无法映射

以上的坑都是我在实际工作中一个一个爬过来的,仅为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Mybatis配置之<properties>属性配置元素解析

    Mybatis配置之<properties>属性配置元素解析

    这篇文章主要介绍了Mybatis配置之<properties>属性配置元素解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Java堆&优先级队列示例讲解(下)

    Java堆&优先级队列示例讲解(下)

    这篇文章主要通过示例详细为大家介绍Java中的堆以及优先级队列,文中的示例代码讲解详细,对我们了解java有一定帮助,需要的可以参考一下
    2022-03-03
  • SpringBoot实现物品点赞功能

    SpringBoot实现物品点赞功能

    这篇文章主要介绍了SpringBoot物品点赞功能实现,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • Java文件上传下载、邮件收发实例代码

    Java文件上传下载、邮件收发实例代码

    这篇文章主要介绍了Java文件上传下载、邮件收发实例代码的相关资料,非常不错具有参考借鉴价值,需要的朋友可以参考下
    2016-06-06
  • 微信支付java版本之JSAPI支付+发送模板消息

    微信支付java版本之JSAPI支付+发送模板消息

    这篇文章主要介绍了微信支付java版本之JSAPI支付,发送模板消息,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • JVM生产环境调优实战案例讲解

    JVM生产环境调优实战案例讲解

    这篇文章主要介绍了JVM生产环境调优实战,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-04-04
  • SpringBoot Mybatis批量插入Oracle数据库数据

    SpringBoot Mybatis批量插入Oracle数据库数据

    这篇文章主要介绍了SpringBoot Mybatis批量插入Oracle数据库数据,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-08-08
  • JUnit测试控制@Test执行顺序的三种方式小结

    JUnit测试控制@Test执行顺序的三种方式小结

    这篇文章主要介绍了JUnit测试控制@Test执行顺序的三种方式小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 使用Java实现生命游戏串行代码示例

    使用Java实现生命游戏串行代码示例

    生命游戏是一种二维细胞自动机,由英国数学家在1970年发明,在游戏的过程中,细胞会形成各种有规律的结构,展现出生命的复杂性和多样性,本文通过java和JavaFX实现了一个简单的生命游戏,可以直观的观察到细胞的迭代过程,需要的朋友可以参考下
    2024-10-10
  • Spring mvc结果跳转方法详解

    Spring mvc结果跳转方法详解

    这篇文章主要介绍了Spring mvc结果跳转方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03

最新评论