我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

关于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();
   }
   
   <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


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

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

基于JDK动态代理原理解析

这篇文章主要介绍了基于JDK动态代理原理解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-05-18

理解JDK动态代理为什么必须要基于接口

这篇文章主要介绍了理解JDK动态代理为什么必须要基于接口,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-11-13

解读jdk动态代理为什么必须实现接口

这篇文章主要介绍了解读jdk动态代理为什么必须实现接口问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-02-17

Java中反射动态代理接口的详解及实例

Java语言中反射动态代理接口的解释与演示Java在JDK1.3的时候引入了动态代理机制、可以运用在框架编程与平台编程时候捕获事件、审核数据、日志等功能实现,首先看一下设计模式的UML图解:当你调用一个接口API时候,实际实现类继承该接口,
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录