首頁 > 軟體

關於feign介面動態代理原始碼解析

2022-03-09 10:00:15

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不寫無法對映

以上的坑都是我在實際工作中一個一個爬過來的,僅為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com