SpringCloud 源碼系列(14)— 服務調用Feign 之 構建@FeignClient接口動態代理

專欄系列文章:SpringCloud系列專欄java

系列文章:web

SpringCloud 源碼系列(1)— 註冊中心Eureka 之 啓動初始化spring

SpringCloud 源碼系列(2)— 註冊中心Eureka 之 服務註冊、續約apache

SpringCloud 源碼系列(3)— 註冊中心Eureka 之 抓取註冊表緩存

SpringCloud 源碼系列(4)— 註冊中心Eureka 之 服務下線、故障、自我保護機制markdown

SpringCloud 源碼系列(5)— 註冊中心Eureka 之 EurekaServer集羣網絡

SpringCloud 源碼系列(6)— 註冊中心Eureka 之 總結篇mvc

SpringCloud 源碼系列(7)— 負載均衡Ribbon 之 RestTemplateapp

SpringCloud 源碼系列(8)— 負載均衡Ribbon 之 核心原理負載均衡

SpringCloud 源碼系列(9)— 負載均衡Ribbon 之 核心組件與配置

SpringCloud 源碼系列(10)— 負載均衡Ribbon 之 HTTP客戶端組件

SpringCloud 源碼系列(11)— 負載均衡Ribbon 之 重試與總結篇

SpringCloud 源碼系列(12)— 服務調用Feign 之 基礎使用篇

SpringCloud 源碼系列(13)— 服務調用Feign 之 掃描@FeignClient註解接口

動態代理工廠組件 FeignClientFactoryBean

從前文中已經分析出 FeignClientFactoryBean 這個組件就是生成 FeignClient 接口動態代理的組件。

FeignClientFactoryBean 實現了 FactoryBean 接口,當一個Bean實現了 FactoryBean 接口後,Spring 會先實例化這個工廠,而後在須要的時候調用 getObject() 建立真正的Bean。

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

}
複製代碼

FeignClientFactoryBean 實現了 getObject() 方法,它又調用了 getTarget() 方法,getTarget() 最後就建立了 FeignClient 接口的動態代理對象。

建立動態代理對象的主要流程以下:

  • 首先獲取了 Feign 上下文 FeignContext,FeignContext 跟 Ribbon 中 SpringClientFactory 是相似的,能夠獲取到每一個服務的上下文。由於每一個服務都有本身的配置、Encoder、Decoder 組件等,因此能夠從 FeignContext 中獲取到當前服務的組件。
  • 而後從 FeignContext 中獲得了 Feign.Builder,這個 Feign.Builder 就是最終用來建立動態代理對象的構造器。
  • @FeignClient 若是沒有配置 url,就會經過服務名稱構造帶服務名的url地址,跟 RestTemplate 相似,最終確定就是走負載均衡的請求;若是配置了 url,就是直接調用這個地址。
  • 都會從 FeignContext 中獲取一個 Client,若是配置了 url,就是獲取 client 裏的代理對象,並設置到 builder 中;不然就直接將 Client 設置到 builder。也就是說根據 url 判斷是否使用負載均衡的 Client。
  • 最終都會調用 Targetertarget() 方法來構造動態代理對象,target 傳入的參數包括當前的 FeignClientFactoryBean 對象、Feign.Builder、FeignContext,以及封裝的 HardCodedTarget 對象。
// 獲取 FeignClient 代理對象的入口
@Override
public Object getObject() throws Exception {
    return getTarget();
}

/** * 建立一個 FeignClient 接口的代理對象,T 就是 @FeignClient 註解的接口類型 * * @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() {
    // Feign 上下文
    FeignContext context = applicationContext.getBean(FeignContext.class);
    // Feign 構造器
    Feign.Builder builder = feign(context);

    // 若是沒有直接配置 url,就走負載均衡請求
    if (!StringUtils.hasText(url)) {
        if (!name.startsWith("http")) {
            url = "http://" + name;
        }
        else {
            url = name;
        }
        // 帶服務名的地址 => http://demo-consumer
        url += cleanPath();
        // 返回的類型確定是具有負載均衡能力的;HardCodedTarget => 硬編碼的 Target
        return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
    }

    // 若是配置了 url,就直接請求 url 地址
    if (StringUtils.hasText(url) && !url.startsWith("http")) {
        url = "http://" + url;
    }
    String url = this.url + cleanPath();
    // Client => Feign 發起 HTTP 調用的核心組件
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            // 獲得的是代理對象,就是原生的 Client.Default
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        if (client instanceof FeignBlockingLoadBalancerClient) {
            // 獲得的是代理對象,就是原生的 Client.Default
            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    // targeter 建立動態代理對象
    return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
複製代碼
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
    // 獲取 Client
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        // Targeter => HystrixTargeter
        Targeter targeter = get(context, Targeter.class);
        // targeter 建立動態代理對象
        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?");
}
複製代碼

動態代理構造器 Feign.Builder

feign() 方法返回了 Feign.Builder,它也是從 FeignContext 中獲取的,這個方法最重要的是設置了 Logger、Encoder、Decoder、Contract,並讀取配置文件中 feign.client.* 相關的配置。FeignClientsConfiguration 中配置了這幾個接口的默認實現類,咱們也能夠自定義這幾個實現類。

protected Feign.Builder feign(FeignContext context) {
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(type);

    // 咱們能夠定製 Logger、Encoder、Decoder、Contract
    Feign.Builder builder = get(context, Feign.Builder.class)
            // required values
            .logger(logger)
            .encoder(get(context, Encoder.class))
            .decoder(get(context, Decoder.class))
            .contract(get(context, Contract.class));

    // 讀取配置文件中 feign.client.* 的配置來配置 Feign
    configureFeign(context, builder);

    return builder;
}
複製代碼

Feign.Builder 的默認實現是什麼呢?從 FeignClientsConfiguration 中能夠知道,默認狀況下就是 Feign.Builder,若是啓用了 feign.hystrix.enabled,那默認實現就是 HystrixFeign.Builder

那 Feign.Builder 和 HystrixFeign.Build 有什麼區別呢?對比下不難發現,主要區別就是建立動態代理的實現類 InvocationHandler 是不一樣的,在啓用 hystrix 的狀況下,會涉及到熔斷、降級等,HystrixFeign.Build 也會設置 @FeignClient 配置的 fallback、fallbackFactory 降級配置類。這塊等後面分析 hystrix 源碼時再來看。如今只須要知道,feign 沒有啓用 hystrix,@FeignClient 配置的 fallback、fallbackFactory 降級回調是不生效的。

public class FeignClientsConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        // 從不重試
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        // 默認爲 Feign.Builder
        return Feign.builder().retryer(retryer);
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {

        // 引入了 hystrix 而且,feign.hystrix.enabled = true
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled")
        public Feign.Builder feignHystrixBuilder() {
            // feign 啓用 hystrix 後,Feign.Builder 就是 HystrixFeign.Builder
            return HystrixFeign.builder();
        }
    }
}
複製代碼

feign 配置

configureFeign() 方法就是配置 Feign.Builder 的,從這個方法能夠驗證基礎篇文章中 feign 配置生效的優先級。

Feign 有三塊配置,一個是能夠經過 Configuration 的方式配置,而後設置到 @FeignClient 的 configuration 參數;而後是全局的 feign.client.default 默認配置,以及服務特定的配置 feign.client.<clientName>

configureFeign() 方法能夠看出,默認狀況下,優先級最低的是代碼配置,其次是默認配置,最高優先級的是服務特定的配置

若是想使代碼配置優先級高於文件中的配置,能夠設置 feign.client.defalut-to-properties=false 來改變 Feign 配置生效的優先級。

protected void configureFeign(FeignContext context, Feign.Builder builder) {
    // 配置文件中 feign.client.* 客戶端配置
    FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);

    FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
    setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());

    if (properties != null && inheritParentContext) {
        // defaultToProperties:優先使用配置文件中的配置
        if (properties.isDefaultToProperties()) {
            // 最低優先級:使用代碼中的 Configuration 配置
            configureUsingConfiguration(context, builder);
            // 次優先級:使用 feign.client.default 默認配置
            configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
            // 高優先級:使用 feign.client.<clientName> 定義的配置
            configureUsingProperties(properties.getConfig().get(contextId), builder);
        }
        // 優先使用Java代碼的配置
        else {
            configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
            configureUsingProperties(properties.getConfig().get(contextId), builder);
            configureUsingConfiguration(context, builder);
        }
    }
    else {
        configureUsingConfiguration(context, builder);
    }
}
複製代碼

網絡調用組件 Client

Clientfeign-core 中的組件,它只有一個接口 execute,這個接口就是調用 Request 的 url,而後將返回接口封裝到 Response中。

public interface Client {

  /** * Executes a request against its {@link Request#url() url} and returns a response. * * @param request safe to replay. * @param options options to apply to this request. * @return connected response, {@link Response.Body} is absent or unread. * @throws IOException on a network error connecting to {@link Request#url()}. */
  Response execute(Request request, Options options) throws IOException;
}
複製代碼

Client 有以下的一些實現類:

Client 的自動化配置類是 FeignRibbonClientAutoConfiguration,FeignRibbonClientAutoConfiguration 導入了 HttpClient、OkHttp 以及默認的 Feign 負載均衡配置類。

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled", matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
}
複製代碼

啓用 Apache HttpClient

HttpClientFeignLoadBalancedConfiguration 的配置能夠看出,要啓用 apache httpclient,需設置 feign.httpclient.enabled=true(默認爲 true),而且須要加入了 feign-httpclient 的依賴(ApacheHttpClient)

啓用 apache httpclient 後,LoadBalancerFeignClient 的代理對象就是 feign-httpclient 中的 ApacheHttpClient

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, HttpClient httpClient) {
        ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
        return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    }
}
複製代碼

啓用 OkHttp

OkHttpFeignLoadBalancedConfiguration 的配置能夠看出,要啓用 okhttp,需設置 feign.okhttp.enabled=true,且須要引入 feign-okhttp 的依賴(OkHttpClient)。

啓用 okhttp 後,LoadBalancerFeignClient 的代理對象就是 feign-okhttp 的 OkHttpClient

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
        OkHttpClient delegate = new OkHttpClient(okHttpClient);
        return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    }
}
複製代碼

默認配置

沒有引入 feign-httpclient 或者 feign-okhttp,就會走默認的 DefaultFeignLoadBalancedConfiguration。而默認的代理對象 Client.Default 其實就是使用 HttpURLConnection 發起 HTTP 調用。

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory);
    }
}
複製代碼

能夠看出,三個配置類建立的 Client 對象都是 LoadBalancerFeignClient,也就是支持負載均衡的請求。只是代理類不一樣,也就是最終發起 HTTP 調用的組件是不一樣的,默認配置下的代理類是 Client.Default,底層就是 HttpURLConnection。

這塊其實跟分析 Ribbon 源碼時,RestTemplate 的負載均衡是相似的。

動態代理目標器 Targeter

Targeter 接口只有一個接口方法,就是經過 target() 方法獲取動態代理對象。Targeter 有 DefaultTargeter、HystrixTargeter 兩個實現類,

interface Targeter {

    <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target);
}
複製代碼

FeignAutoConfiguration 配置類中可看到,只要引入了 HystrixFeign,Targeter 的默認實現就是 HystrixTargeter

HystrixTargeter 一看就是用來整合 feign 和 hystrix 的,使 feign 調用能夠實現熔斷、限流、降級。

public class FeignAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    protected static class HystrixFeignTargeterConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new HystrixTargeter();
        }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    protected static class DefaultFeignTargeterConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new DefaultTargeter();
        }
    }
}
複製代碼

能夠看到 HystrixTargeter 和 DefaultTargeter 的區別就在於 HystrixTargeter 會向 Feign.Builder 設置降級回調處理類,這樣 feign 調用觸發熔斷、降級時,就能夠進入回調類處理。

它們本質上最終來講都是調用 Feign.Builder 的 target() 方法建立動態代理對象。

class HystrixTargeter implements Targeter {

    @Override
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) {
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            // 非 HystrixFeign.Builder 類型,就直接調用 target 方法
            return feign.target(target);
        }
        // Feign 啓用了 hystrix 後,就會向 HystrixFeign.Builder 設置回調類或回調工廠
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName() : factory.getContextId();
        
        Class<?> fallback = factory.getFallback();
        // 設置回調類
        if (fallback != void.class) {
            return targetWithFallback(name, context, target, builder, fallback);
        }
        // 設置回調工廠類
        Class<?> fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
        }
        // 調用 Feign.Builder 建立動態代理
        return feign.target(target);
    }
}
複製代碼
class DefaultTargeter implements Targeter {

    @Override
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) {
        return feign.target(target);
    }
}
複製代碼

Feign.Builder 建立動態代理

前面已經分析出,Feign.Builder 的默認實現就是 Feign.Builder,HystrixTargeter 中調用了 Feign.Builder 的 target 方法來建立動態代理。

  • target 方法中首先調用 build() 方法構建出 Feign,而後調用 Feign 的 newInstance 建立動態代理對象。
  • build() 方法中首先讀取配置的 Client、Retryer、Logger、Contract、Encoder、Decoder 等對象。
  • 而後獲取了 InvocationHandlerFactory,默認就是 InvocationHandlerFactory.Default,這是 feign 提供的一個工廠類來建立代理對象 InvocationHandler
  • 接着建立了接口方法處理器工廠 SynchronousMethodHandler.Factory,它就是用來將接口方法封裝成一個方法執行器 MethodHandler,默認實現類是 SynchronousMethodHandler
  • 還建立了 springmvc 註解處理器 ParseHandlersByName,可想而知,這就是用來處理接口中的 springmvc 註解的,將 REST 接口解析生成 MethodHandler。
  • 最後建立了 Feign 對象,實現類是 ReflectiveFeign,以後就是使用 ReflectiveFeign 來建立動態代理對象了。
public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

// 構建 Feign
public Feign build() {
    // Feign Http調用客戶端,默認爲 Client.Default
    Client client = Capability.enrich(this.client, capabilities);
    // 重試器,默認是重不重試
    Retryer retryer = Capability.enrich(this.retryer, capabilities);
    // Feign 請求攔截器,能夠對 Feign 請求模板RequestTemplate作一些定製化處理
    List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
      .map(ri -> Capability.enrich(ri, capabilities))
      .collect(Collectors.toList());
    // 日誌組件,默認爲 Slf4jLogger
    Logger logger = Capability.enrich(this.logger, capabilities);
    // 接口協議組件,默認爲 SpringMvcContract
    Contract contract = Capability.enrich(this.contract, capabilities);
    // 配置類
    Options options = Capability.enrich(this.options, capabilities);
    // 編碼器
    Encoder encoder = Capability.enrich(this.encoder, capabilities);
    // 解碼器
    Decoder decoder = Capability.enrich(this.decoder, capabilities);
    // 建立 InvocationHandler 的工廠類
    InvocationHandlerFactory invocationHandlerFactory =
      Capability.enrich(this.invocationHandlerFactory, capabilities);
    QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
    // 接口方法處理器工廠
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
          logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
    // 解析 springmvc 註解
    ParseHandlersByName handlersByName =
      new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
          errorDecoder, synchronousMethodHandlerFactory);
    // ReflectiveFeign
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
複製代碼

InvocationHandlerFactory 包含一個 create 接口方法,默認實現是 InvocationHandlerFactory.Default,返回的 InvocationHandler 類型是 ReflectiveFeign.FeignInvocationHandler

package feign;

public interface InvocationHandlerFactory {

  // 建立動態代理
  InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);

  // 方法處理器
  interface MethodHandler {

    Object invoke(Object[] argv) throws Throwable;
  }

  static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }
}
複製代碼

接着看 ReflectiveFeign 的 newInstance() 方法:

  • newInstance 的參數 target 就是前面封裝的 Target.HardCodedTarget,它封裝了客戶端的類型、url等屬性。
  • 首先是使用 ParseHandlersByName 將 FeignClient 接口中的接口轉換成 MethodHandler,實際類型就是 SynchronousMethodHandler,這個細節就不在看了。
  • 而後用 InvocationHandlerFactory 建立 InvocationHandler 代理對象,也就是 ReflectiveFeign.FeignInvocationHandler,調用動態代理對象的方法,最終都會進入到這個執行處理器裏面。
  • 最後,終於看到建立動態代理的地方了,使用 Proxy 建立了 FeignClient 的動態代理對象,這個動態代理的類型就是 @FeignClient 註解的接口的類型。最後被注入到 IoC 容器後,就能夠在代碼中注入本身編寫的 FeignClient 客戶端組件了。

最終就是經過 Proxy 建立一個實現了 FeignClient 接口的動態代理,而後全部接口方法的調用都會被 FeignInvocationHandler 攔截處理。

public <T> T newInstance(Target<T> target) {
    // 使用 ParseHandlersByName 將 FeignClient 接口中的接口轉換成 MethodHandler,springmvc 註解由 Contract 組件處理
    // MethodHandler => SynchronousMethodHandler
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    // 轉換成 Method - MethodHandler 映射
    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)));
      }
    }
    // 用 SynchronousMethodHandler.Factory 建立 SynchronousMethodHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 用 Proxy 建立動態代理,動態代理對象就是 SynchronousMethodHandler
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}
複製代碼

一張圖總結 FeignClient 生成動態代理的流程

下面用一張圖來總結下生成 FeignClient 動態代理的流程:

  • 首先 @EnableFeignClients 導入的註冊器 FeignClientsRegistrar 會掃描 @FeignClient 註解的接口,並生成 FeingClientFactoryBeanBeanDefinition 註冊到容器中。最後會調用 FeingClientFactoryBean 的 getObject 方法來獲取接口的動態代理對象。
  • 進入 FeingClientFactoryBean 的 getObject 方法,首先獲取了 FeignContext,它其實就是每一個客戶端的容器,相似於一個 Map 結構,緩存了客戶端與容器間的關係,後續大部分組件都是從 FeignContext 中獲取。
  • 從 FeignContext 中獲取 Feign 構造器 Feign.Builder,並配置 Feign.Builder,配置來源有多個地方,優先級最高的是 application.yml 中的配置生效;也能夠配置 feign.client.default-to-properties=false 設置Java代碼配置爲高優先級。
  • 接下來就要根據 @FeignClient 是否配置了 url 決定是否走負載均衡的請求,其實就是設置的 Client 不同:
    • 若是配置了 url,表示一個具體的地址,就使用將 LoadBalancerFeignClient 的 delegate 做爲 Client 設置給 Feign.Builder。
    • 若是沒有配置 url,表示經過服務名請求,就將 LoadBalancerFeignClient 做爲 Client 設置給 Feign.Builder。
  • 再從 FeignContext 中獲取 Targeter,調用它的 target 方法來獲取動態代理。
  • 在 target 方法中,先調用 Feign.Builder 的 build() 方法構建了 ReflectiveFeign
    • 先是獲取代理對象工廠 InvocationHandlerFactory,用於建立 InvocationHandler
    • 而後用各個組件,構造了方法處理器工廠 SynchronousMethodHandler.Factory,接着建立了方法解析器 ParseHandlersByName
    • 最後基於 InvocationHandlerFactory 和 ParseHandlersByName 構造了 ReflectiveFeign
  • 最後調用 ReflectiveFeign 的 newInstance 方法反射建立接口的動態代理:
    • 先用方法解析器 ParseHandlersByName 解析接口,將接口解析成 SynchronousMethodHandler
    • 接着使用 InvocationHandlerFactory 建立了代理對象 InvocationHandler(ReflectiveFeign.FeignInvocationHandler)
    • 最終用 Proxy 建立動態代理對象,對象的類型就是接口的類型,代理對象就是 ReflectiveFeign.FeignInvocationHandler

相關文章
相關標籤/搜索