关于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不写无法映射

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

相关文章

  • Java事件机制要素及实例详解

    Java事件机制要素及实例详解

    这篇文章主要介绍了Java事件机制要素及实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • SWT(JFace)体验之List演示汇总

    SWT(JFace)体验之List演示汇总

    SWT(JFace)体验之List演示代码汇总
    2009-06-06
  • Java方法签名为何不包含返回值类型

    Java方法签名为何不包含返回值类型

    这篇文章主要介绍了Java方法签名为何不包含返回值类型,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • JavaWeb中的常用的请求传参注解说明

    JavaWeb中的常用的请求传参注解说明

    这篇文章主要介绍了JavaWeb中的常用的请求传参注解说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • Thread线程的基础知识及常见疑惑点总结

    Thread线程的基础知识及常见疑惑点总结

    在本篇内容里小编给大家分享的是关于Thread线程的基础知识及常见疑惑点,对此有学习需求的朋友们可以学习参考下。
    2019-05-05
  • Spring事务失效的场景梳理总结

    Spring事务失效的场景梳理总结

    实际项目开发中,如果涉及到多张表操作时,为了保证业务数据的一致性,大家一般都会采用事务机制,好多小伙伴可能只是简单了解一下,遇到事务失效的情况,便会无从下手,下面这篇文章主要给大家介绍了关于Spring事务失效场景的相关资料,需要的朋友可以参考下
    2023-02-02
  • 在mybatis中使用mapper进行if条件判断

    在mybatis中使用mapper进行if条件判断

    这篇文章主要介绍了在mybatis中使用mapper进行if条件判断,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • springmvc处理响应数据的解析

    springmvc处理响应数据的解析

    今天小编就为大家分享一篇关于springmvc处理响应数据的解析,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • 详解SpringBoot如何自定义Starter

    详解SpringBoot如何自定义Starter

    Starter是Spring Boot中的一个非常重要的概念,Starter相当于模块,它能将模块所需的依赖整合起来并对模块内的Bean根据环境( 条件)进行自动配置。本文将介绍SpringBoot如何自定义Starter,感兴趣的可以学习一下
    2021-12-12
  • 分享40个Java多线程问题小结

    分享40个Java多线程问题小结

    多个线程共存于同一JVM进程里面,所以共用相同的内存空间,较之多进程,多线程之间的通信更轻量级,本文给大家分享40个Java多线程问题小结 的相关资料,需要的朋友可以参考下
    2015-12-12

最新评论