java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > feign接口动态代理源码解析

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

作者: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不写无法映射

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

您可能感兴趣的文章:
阅读全文