註解 @EnableFeignClients 工做原理

本文分析所用源代碼基於Spring Cloud 2.1.0 RELEASEjava

概述

在Spring cloud應用中,當咱們要使用feign客戶端時,通常要作如下三件事情 :spring

使用註解@EnableFeignClients啓用feign客戶端;
示例 :bash

@SpringBootApplication
@EnableFeignClients
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}

使用註解@FeignClient 定義feign客戶端 ;
示例 : 該例子定義了一個feign客戶端,將遠程服務http://test-service/test/echo映射爲一個本地Java方法調用。app

@FeignClient(name = "test-service", path = "/test")
public interface TestService {
    @RequestMapping(value = "/echo", method = RequestMethod.GET)
    TestModel echo(@RequestParam("parameter") String parameter);
}

使用註解@Autowired使用上面所定義feign的客戶端 ; 負載均衡

  @Autowired   
    TestService testService;

    public void run()
    {
        // 這裏的使用本地Java API的方式調用遠程的Restful接口
        TestModel dto = testService.echo("Hello,你好!");
        log.info("echo : {}", dto);
     }

上面的三個步驟,前兩個步驟能夠理解爲定義feign客戶端,第三步是使用所定義的feign客戶端。經過調試發現,上面第三步所注入的testService是一個代理對象,以下所示 :框架

testService = {$Proxy66@5502} 
    "HardCodedTarget(type=TestService, name=test-service, url=http://test-service/test)"
 h = {ReflectiveFeign$FeignInvocationHandler@6924} 
  target = {Target$HardCodedTarget@6930} 
  dispatch = {LinkedHashMap@6931}  size = 1
   0 = {LinkedHashMap$Entry@6948} 
       "public abstract xxx.model.TestModel xxx.service.TestService.echo(java.lang.String)" 

該對象會代理客戶端完成遠程服務方法的調用,那麼,該代理對象是如何生成的 ?這篇文章,咱們經過源代碼解析來回答這兩個問題。ide

源代碼解析
註解@EnableFeignClients:掃描和註冊feign客戶端bean定義
註解@EnableFeignClients用於告訴框架掃描全部經過註解@FeignClient定義的feign客戶端。它又經過註解@Import導入了類FeignClientsRegistrar( feign客戶端註冊器),以下所示:函數

@EnableFeignClients 
    => @Import(FeignClientsRegistrar.class)


FeignClientsRegistrar : feign客戶端註冊器
FeignClientsRegistrar實現了接口 ImportBeanDefinitionRegistrar。而ImportBeanDefinitionRegistrar的設計目的,就是被某個實現類實現,配合@Configuration註解的配置類,在配置類被處理時,用於額外註冊一部分bean定義:工具

對於上面的例子,使用者配置類就是 TestApplicationpost

public interface ImportBeanDefinitionRegistrar {

   /**
    * Register bean definitions as necessary based on the given annotation metadata of
    * the importing @Configuration class.
    * 根據使用者配置類的註解元數據註冊bean定義
    * @param importingClassMetadata 使用者配置類的註解元數據
    * @param registry 當前bean定義註冊表,通常指當前Spring應用上下文對象,當前Spring容器
    */
   public void registerBeanDefinitions(
           AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

#registerBeanDefinitions – 註冊feign客戶端配置和feign客戶端
方法FeignClientsRegistrar#registerBeanDefinitions實現以下:

 @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
       // 註冊缺省配置到容器 registry
       registerDefaultConfiguration(metadata, registry);
       // 註冊所發現的各個 feign 客戶端到到容器 registry
       registerFeignClients(metadata, registry);
   }

#registerDefaultConfiguration– 註冊feign客戶端缺省配置
    

// 註冊feign客戶端的缺省配置,缺省配置信息來自注解元數據的屬性 defaultConfiguration    
    private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
        // 獲取註解@EnableFeignClients的註解屬性     
        Map<String, Object> defaultAttrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            // 下面是對所註冊的缺省配置的的命名,格式以下 :
            // default.xxx.TestApplication
            if (metadata.hasEnclosingClass()) {
                //  針對註解元數據metadata對應一個內部類或者方法返回的方法本地類的情形
                name = "default." + metadata.getEnclosingClassName();
            }
            else {        
                // name 舉例 : default.xxx.TestApplication
                // 這裏 xxx.TestApplication 是註解@EnableFeignClients所在配置類的長名稱            
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }

#registerDefaultConfiguration方法最終註冊客戶端缺省配置的動做交給方法#registerDefaultConfiguration執行。

#registerClientConfiguration – 註冊feign客戶端配置
   

// 將指定feign客戶端配置configuration做爲一個bean定義註冊到容器:
    // bean 定義對象類型 : GenericBeanDefinition
    // bean class : FeignClientSpecification    
    // bean name : default.xxx.TestApplication.FeignClientSpecification (缺省配置)
    // bean name : test-service.FeignClientSpecification (針對某個feign client 的配置)
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
            Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(
                name + "." + FeignClientSpecification.class.getSimpleName(),
                builder.getBeanDefinition());
    }

#registerDefaultConfiguration方法用於註冊一個feign客戶端配置bean,能夠用於註冊針對全部feign客戶端的缺省配置的註冊,也能夠用於針對每一個feign客戶端的專有配置的註冊。

針對全部feign客戶端的缺省配置的bean名稱相似於 : default.xxx.TestApplication.FeignClientSpecification,
針對某個名稱爲test-service的feign客戶端的配置的bean名稱相似於:test-service.FeignClientSpecification。#registerFeignClients – 註冊各個feign客戶端及其配置
  

 // 參數 metadata : 註解@EnableFeignClients所在配置類的註解元數據
    public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
        // 定義一個基於classpath的組件掃描器,它會根據指定的掃描位置和@EnableFeignClients註解屬性   
        // 找出開發人員定義的全部feign客戶端,也就是那些使用了註解@FeignClient的全部接口定義
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        Set<String> basePackages;

        // attrs 用於表示註解@EnableFeignClients所在配置類的註解元數據中註解@EnableFeignClients
        // 的部分
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
           // @EnableFeignClients 中沒有指定 clients 屬性的狀況
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = getBasePackages(metadata);
        }
        else {
           // @EnableFeignClients 中指定了 clients 屬性的狀況
            final Set<String> clientClasses = new HashSet<>();
            basePackages = new HashSet<>();
            for (Class<?> clazz : clients) {
                basePackages.add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }
            AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                @Override
                protected boolean match(ClassMetadata metadata) {
                    String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                    return clientClasses.contains(cleaned);
                }
            };
            scanner.addIncludeFilter(
                    new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        }

        // 使用 scanner 掃描每個 basePackage, 獲取其中的 feign 客戶端定義, 
        // 也就是 @FeignClient 定義的那些接口
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner
                    .findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Assert.isTrue(annotationMetadata.isInterface(),
                            "@FeignClient can only be specified on an interface");

                    // 獲取所定義的feign客戶端接口上的註解@FeignClient屬性
                    Map<String, Object> attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());

                    String name = getClientName(attributes);
                    // 將所定義的feign客戶端上的配置屬性做爲一個bean註冊到容器   
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));

                    // 將所定義的feign客戶端做爲一個bean註冊到容器:
                    // bean 定義類型 : GenericBeanDefinition
                    //  bean class : FeignClientFactoryBean
                    //  autowire 模式 : 根據類型綁定
                    // @FeignClient註解中的url,path,fallback等屬性會設置爲bean定義的屬性
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }   

    // 輔助工具類,從@EnableFeignClients註解屬性中獲取basePackages屬性:
    // 參考如下@EnableFeignClients註解屬性 :
    // 1. value
    // 2. basePackages
    // 3. basePackageClasses
    // 4. 配置類所在的包
    // 參數 importingClassMetadata : 使用註解@EnableFeignClients的配置類的元數據
    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        // 註解@EnableFeignClients的屬性
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }

        if (basePackages.isEmpty()) {
            basePackages.add(
                    ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        return basePackages;
    }


#registerFeignClients 最終註冊feign客戶端配置的動做交給#registerClientConfiguration完成,註冊feign客戶端的動做交給#registerFeignClient方法完成。#registerFeignClient – 註冊一個feign客戶端

    // 將所定義的feign客戶端做爲一個bean註冊到容器:
    // bean 定義類型 : GenericBeanDefinition
    //  bean class : FeignClientFactoryBean -- 這是一個工廠bean,而不是最終bean實例的class
    //  autowire 模式 : 根據類型綁定
    // @FeignClient註解中的url,path,fallback等屬性會設置爲bean定義的屬性
    // 參數 registry : Spring 容器
    // 參數 annotationMetadata : @FeignClient所註解的接口上的註解元數據
    // 參數 attributes : @FeignClient 註解屬性信息
    private void registerFeignClient(BeanDefinitionRegistry registry,
           AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
       String className = annotationMetadata.getClassName();
       BeanDefinitionBuilder definition = BeanDefinitionBuilder
               .genericBeanDefinition(FeignClientFactoryBean.class);
       validate(attributes);
       definition.addPropertyValue("url", getUrl(attributes));
       definition.addPropertyValue("path", getPath(attributes));
       String name = getName(attributes);
       definition.addPropertyValue("name", name);
       definition.addPropertyValue("type", className);
       definition.addPropertyValue("decode404", attributes.get("decode404"));
       definition.addPropertyValue("fallback", attributes.get("fallback"));
       definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
       definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

       String alias = name + "FeignClient";
       AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

       boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

       beanDefinition.setPrimary(primary);

       String qualifier = getQualifier(attributes);
       if (StringUtils.hasText(qualifier)) {
           alias = qualifier;
       }

       BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
               new String[] { alias });
       BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
   }

從上面的代碼分析可知,FeignClientsRegistrar的主要做用以下 :

註冊缺省feign客戶端配置bean定義;
對於每一個@FeignClient註解的feign客戶端定義 :
註冊一個針對該feign客戶端的配置bean定義;
註冊該feign客戶端bean定義,生成bean採用工廠類FeignClientFactoryBean;
並且,上述功能實如今FeignClientsRegistrar實現的接口ImportBeanDefinitionRegistrar所定義的方法registerBeanDefinitions中。而該方法會在應用的@EnableFeignClients註解被處理時被調用執行。具體的執行時調用棧以下所示:

   

AbstractApplicationContext#invokeBeanFactoryPostProcessors
    => PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
    => foreach BeanDefinitionRegistryPostProcessor : #postProcessBeanDefinitionRegistry
    => ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
    => #processConfigBeanDefinitions
    => ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
    => foreach ConfigurationClass : #loadBeanDefinitionsForConfigurationClass
    => #loadBeanDefinitionsFromRegistrars
    => foreach ImportBeanDefinitionRegistrar : #registerBeanDefinitions
    => FeignClientsRegistrar#registerBeanDefinitions

FeignClientFactoryBean生成feign客戶端代理對象
基於上面的分析,咱們能夠得知,開發人員所定義的feign客戶端和相關配置會以bean定義的形式註冊到bean容器中,這樣當使用@Autowired注入一個feign客戶端時,容器會使用工廠類FeignClientFactoryBean爲其生成一個實例。下面咱們來看其具體工做過程。

FeignClientFactoryBean#getObject生成feign客戶端代理對象
    // 該方法由接口FactoryBean約定
       @Override
    public Object getObject() throws Exception {
        return getTarget();
    }
    
    
    <T> T getTarget() {
        //  從應用上下文中獲取建立 feign 客戶端的上下文對象 FeignContext
        // FeignContext 針對每一個feign客戶端定義會生成一個不一樣的 AnnotationConfigApplicationContext,
        // 這些應用上下文的parent都設置爲當前應用的主應用上下文
        // 參考 : FeignAutoConfiguration
        FeignContext context = applicationContext.getBean(FeignContext.class);
        // 爲目標feign客戶端對象構建一個 builder,該builder最終生成的目標feign客戶端是一個
        // 動態代理,使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(this.url)) {
           // @FeignClient 屬性 url 屬性沒有指定的狀況         
           // 根據屬性 name , path 拼裝一個 url,
           // 這種一般是須要在多個服務節點之間進行負載均衡的狀況
            if (!this.name.startsWith("http")) {
                url = "http://" + this.name;
            }
            else {
                url = this.name;
            }
          // 方法cleanPath()加工屬性path,使其以/開頭,不以/結尾
            url += cleanPath();
           // 這裏造成的url格式相似 :  http://test-service/test
           // 其中 test-service 是服務名,不是服務所在節點的IP,主機名或者域名
           // 函數loadBalance 作以下動做 :
           // 1. 將builder和一個LoadBalancerFeignClient bean實例關聯起來
           // 2. 使用一個HystrixTargeter將builder和一個 HardCodedTarget bean實例關聯起來
           // 這裏 HardCodedTarget 表示對應 url 爲 http://test-service/test 的遠程服務(可能
           // 包含多個服務方法)
           // 3. 生成最終的feign client 實例 : ReflectiveFeign$FeignInvocationHandler 的動態代理對象,
           // 使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler。
           // 每一個遠程服務方法會對應到一個@FeignClient註解的接口方法上(依據方法上的註解進行匹配)
            return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, url));
        }
        
        // @FeignClient 屬性 url 屬性被指定的狀況 
        // 這種一般是明確指出了服務節點的url的狀況,實際上不須要負載均衡
        if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
            this.url = "http://" + this.url;
        }
        String url = this.url + cleanPath();
        // 將builder和一個LoadBalancerFeignClient bean實例關聯起來
        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
                // 由於指定了明確的服務節點url,因此這裏不須要負載均衡,
                // 因此這裏儘管client是LoadBalancerFeignClient,因此
                // 實際上能夠獲取其所代理的對象做爲最終的client,
                // 至關於去掉了LoadBalancerFeignClient這層的代理功能
                client = ((LoadBalancerFeignClient)client).getDelegate();
            }
            builder.client(client);
        }
        // 使用一個HystrixTargeter將builder和一個 HardCodedTarget bean實例關聯起來
        Targeter targeter = get(context, Targeter.class);
        // 生成最終的feign client 實例 : ReflectiveFeign$FeignInvocationHandler 的動態代理對象,
        // 使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler。
        // 每一個遠程服務方法會對應到 一個@FeignClient註解的接口方法上(依據方法上的註解進行匹配)        
        return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
                this.type, this.name, url));
    }

方法FeignClientFactoryBean#feign – 建立feign客戶端構建器

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

        // 從上下文獲取一個 Feign.Builder 上,
        // 並從上下文得到 Encoder, Decoder, Contract 設置到該 builder 上
        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));
        
        // 對 builder 進行其餘屬性設置
        configureFeign(context, builder);

        return builder;
    }

方法FeignClientFactoryBean#loadBalance – 生成具有負載均衡能力的feign客戶端
爲feign客戶端構建器綁定負載均衡客戶端,綁定目標服務端點,並生成最終的feign客戶端實例。

// 對builder設置負載均衡客戶端,綁定到目標服務端點,構建最終的feign客戶端對象
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
        // 從上下文context獲取一個Client,缺省是 LoadBalancerFeignClient     
        Client client = getOptional(context, Client.class);
        if (client != null) {
            // 將client設置到builder上
            builder.client(client);
            // 從上下文中獲取一個 targeter,缺省是一個 HystrixTargeter
            Targeter targeter = get(context, Targeter.class);
            // 上面獲取獲得的 targeter 會根據 builder 的類型決定如何將 target
            // 綁定到 builder 並設置有關的其餘屬性和功能,而後生成最終的feign客戶端對象
            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客戶端構建器類爲Feign.Builder,而且Targeter是一個HystrixTargeter。HystrixTargeter#target方法的參數builder爲Feign.Builder時,會直接調用該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)) {
            return feign.target(target);
        }
       // ... 省略其餘代碼            
    }
}

接下來再來看Feign.Builder#target是如何工做的。

 

// 執行構建而且建立相應的feign客戶端實例    
  public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }
 
  // 構建過程,最終根據各類配置生成一個 ReflectiveFeign 對象       
  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);
    }
  }  

而後再看ReflectiveFeign#newInstance方法。 

// 建立最終的feign客戶端實例 : 一個 ReflectiveFeign$FeignInvocationHandler 的動態代理對象
  @Override
  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 
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
          // 對於每一個對應服務功能端點的方法,缺省使用nameToHandler獲取的MethodHandler,缺省是
          // SynchronousMethodHandler
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    // 建立feign客戶端實例 ReflectiveFeign$FeignInvocationHandler,
    // 該對象包含了上面所建立的methodToHandler,用於對應各個開發者定義的@FeignClient接口方法
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 建立feign客戶端實例的動態代理對象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    // 將缺省方法處理器綁定到feign客戶端實例的動態代理對象上
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

從上面的分析咱們不難看出,爲何最終注入的testService最終是一個ReflectiveFeign$FeignInvocationHandler動態代理實例了。

總結

從上面的分析能夠看出,當咱們使用註解@EnableFeignClients 時,至關於啓用了feign客戶端定義的掃描和註冊機制,從而能夠發現開發人員經過註解@FeignClient定義的feign客戶端,並最終做爲bean定義註冊到容器中。而經過@Autowired自動裝配註解,這些feign客戶端會以ReflectiveFeign$FeignInvocationHandler動態代理的形式被注入到使用方。該feign客戶端包含了對每一個接口方法的處理器MethodHandler,接口缺省方法對應DefaultMethodHandler,服務功能端點方法對應SynchronousMethodHandler。

參考文章
Spring Cloud feign客戶端執行流程概述  

相關文章
相關標籤/搜索