二、openfeign生成並調用客戶端動態代理對象


所有文章

https://www.cnblogs.com/lay2017/p/11908715.html

 

正文

上一篇文章中,我們了解到了@FeignClient注解的接口被掃描到以后,會生成一個FeignClientFactoryBean的BeanDefinition。然后,spring將會通過調用FeignClientFactoryBean的getObject方法來獲取@FeignClient注解的接口對應的代理對象。

生成proxy對象

本文,從FeignClientFactoryBean的getObject方法開始,看看代理對象的生成。跟進getObject方法

public Object getObject() throws Exception {
    return getTarget();
}

繼續跟進getTarget,該方法做了一些預處理。獲取了一個上下文以及Feign的構造器,沒有URL的情況下拼接了一個

FeignContext是在FeignAutoConfiguration被解析的時候成為Bean的

<T> T getTarget() {
    // 獲取一個上下文
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    // feign用於構造代理對象,builder將會構建feign
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
        // 拼接URL地址,如:http://service-provider/
        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        } else {
            this.url = this.name;
        }
        this.url += cleanPath();
        
        return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url));
    }
    // ... 省略
}

 

預處理之后,進入loadBalance方法

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
    // 獲取執行http請求的客戶端
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        // 選擇獲取代理對象的實現類,默認是HystrixTargeter
        Targeter targeter = get(context, Targeter.class);
        return targeter.target(this, builder, context, target);
    }

    throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

獲取代理對象的實現由Targeter的實現類處理,默認是HystrixTargeter

 

跟進HystrixTargeter的target方法

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);
    }
    // ...

    return feign.target(target);
}

前面說過,Feign實現了構造代理對象的過程,所以這里將會回調feign的構造過程方法

 

跟進feign的target方法,build將會構造出Feign對象,而newInstance會返回代理對象

public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

public Feign build() {
  // ...
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

 

跟進newInstance方法,看看代理對象是如何被構建的

public <T> T newInstance(Target<T> target) {
    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)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    // jdk的動態代理獲取的代理對象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}

代理對象的構建主要由三塊內容

1、構建Method到MethodHandler的映射關系,后面調用代理的對象的時候將會根據Method找到MethodHandler然后調用MethodHandler的invoke方法,而MethodHandler將包含發起http請求的實現。

2、jdk動態代理需要提供InvocationHandler,這個大家比較熟悉了。而InvocationHandler將由InvocationHandlerFactory的create方法實現

3、通過Proxy.newProxyInstance方法,生成proxy對象。

這里我們看看factory.create方法生成InvocationHandler的實現吧

static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      // 這是一個內部類的實現
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }

 

調用proxy對象發起http請求

我們知道,jdk的動態代理將會調用FeignInvocationHandler的invoke方法。所以,我們看看FeignInvocationHandler是怎么調用Method的

private final Map<Method, MethodHandler> dispatch;

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // ...

  return dispatch.get(method).invoke(args);
}

前面我們說到,構建proxy對象的時候會構建Method和MethodHandler的映射關系。而這里invoke代理對象的時候又會根據method來獲取到MethodHandler,再調用其invoke方法。

MethodHandler的默認實現類是SynchronousMethodHandler,我們跟進它的invoke方法

public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        // ...
      }
    }
  }

熟悉的代碼來了,executeAndDecode將會負責發起http請求

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);

    Response response;

    try {
         // 執行http請求
      response = client.execute(request, options);
    } catch (IOException e) {
      
    }

    try {
      //...

      // http請求成功
      if (response.status() >= 200 && response.status() < 300) {
          // 無需返回值
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          // 解碼結果
          Object result = decode(response);

          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        // ...
      } else {
        // ...
      }
    } catch (IOException e) {
      // ...
    } finally {
      // ...
    }
  }

 

總結

openFeign生成@FeignClient注解的接口的代理對象是從FeignClientFactoryBean的getObject方法開始的,生成proxy對象主要由ReflectiveFeign對象來實現。動態代理方法由jdk原生的動態代理支持。

調用proxy對象,其實就是發起http請求,請求結果將被解碼並返回。

所以,正如Feign本身的意義一樣,http遠程調用被偽裝成了本地調用一樣簡單的代理對象,對於使用者來說就是調用本地接口一樣簡單。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM