【SpringCloud原理】萬字剖析OpenFeign之FeignClient動態代理生成原始碼

2022-05-25 06:01:48

 前面時候我釋出兩篇關於nacos原始碼的文章,一篇是聊一聊nacos是如何進行服務註冊的,另一篇是一文帶你看懂nacos是如何整合springcloud -- 註冊中心篇。今天就繼續接著剖析SpringCloud中OpenFeign元件的原始碼,來聊一聊OpenFeign是如何工作的。

一、@EnableFeignClinets作用原始碼剖析

我們都知道,要使用feign,必須要使用@EnableFeignClinets來啟用,這個註解其實就是整個feign的入口,接下來我們著重分析一下這個註解幹了什麼事。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}

 這個註解通過@Import註解匯入一個設定類FeignClientsRegistrar.class,FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar介面,所以Spring Boot在啟動的時候,會去呼叫FeignClientsRegistrar類中的registerBeanDefinitions來動態往spring容器中注入bean。如果有不懂小夥伴可以看一下我以前寫過的一篇文章 看Spring原始碼不得不會的@Enable模組驅動實現原理講解,這裡詳細講解了@Import註解的作用。

接下來看一下registerBeanDefinitions的實現

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry)
    //這個方式是注入一些設定,就是對EnableFeignClients註解屬性的解析
    registerDefaultConfiguration(metadata, registry);
    //這個方法是掃秒加了@FeignClient註解
    registerFeignClients(metadata, registry);
}

這裡我們著重分析registerFeignClients,看一看是如何掃描@FeignClient註解的,然後掃描到之後又做了什麼。

public void registerFeignClients(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    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) {
      scanner.addIncludeFilter(annotationTypeFilter);
      basePackages = getBasePackages(metadata);
    }
    else {
      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)));
    }

    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");

          Map<String, Object> attributes = annotationMetadata
              .getAnnotationAttributes(
                  FeignClient.class.getCanonicalName());

          String name = getClientName(attributes);
          registerClientConfiguration(registry, name,
              attributes.get("configuration"));

          registerFeignClient(registry, annotationMetadata, attributes);
        }
      }
    }
  }

這段程式碼我分析一下,先獲取到了一個ClassPathScanningCandidateComponentProvider這個物件,這個物件是按照一定的規則來掃描指定目錄下的類的,符合這個規則的每個類,會生成一個BeanDefinition,不知道BeanDefinition的小夥伴可以看我之前寫的關於bean生命週期的文章 Spring bean到底是如何建立的?(上)和 Spring bean到底是如何建立的?(下),裡面有過對BeanDefinition的描述。

獲取到ClassPathScanningCandidateComponentProvider物件,設定這個物件,指定這個物件需要掃描出來標有@FeignClient註解的類;隨後解析EnableFeignClients註解,獲取內部的屬性,獲取到指定的需要掃描包路徑下,如果沒有指定的,那麼就預設是當前註解所在類的所在目錄及子目錄。

然後就遍歷每個目錄,找到每個標有@FeignClient註解的類,對每個類就生成一個BeanDefinition,可以把BeanDefinition看成對每個標有@FeignClient註解的類資訊的封裝。

拿到一堆BeanDefinition之後,會遍歷BeanDefinition,然後呼叫registerClientConfiguration和registerFeignClient方法。

接下來我分別剖析一下這兩個方法的作用

registerClientConfiguration:

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());
  }

這裡的作用就是拿出你再@FeignClient指定的設定類,也就是configuration屬性,然後構建一個bean class為FeignClientSpecification,傳入設定。這個類的最主要作用就是將每個Feign的使用者端的設定類封裝成一個FeignClientSpecification的BeanDefinition,註冊到spring容器中。記住這個FeignClientSpecification,後面會有用。

registerFeignClient:

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);
    String contextId = getContextId(attributes);
    definition.addPropertyValue("contextId", contextId);
    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 = contextId + "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);
}

registerFeignClient這個方法很重要我來說一下大概做了哪些事重新構造了一個BeanDefinition這個BeanDefinition的指定的class型別是FeignClientFactoryBean這個類實現了FactoryBean介面對spring有一定了解的小夥伴應該知道spring在生成bean的時候判斷BeanDefinition中bean的class如果是FactoryBean的實現的話會呼叫這個實現類的getObject來獲取物件這裡我就不展開講了不瞭解的同學可以記住這個結論

到這一步,@EnableFeignClinets的作用就說完了。這個類的主要作用是掃描指定(不指定就預設路徑下的)所有加了@FeignClient註解的類,然後每個類都會生成一個BeanDefinition,隨後遍歷每個BeanDefinition,然後取出每個@FeignClient註解的屬性,構造新的BeanDefinition,傳入FeignClientFactoryBean的class,隨後注入到spring容器中;同時有設定類的也會將設定類構件出一個bean class為FeignClientSpecification的BeanDefinition注入到spring容器中。

為了便於理解,我這裡畫個圖來總結一下這個註解幹了什麼事。

二、Feign使用者端介面動態代理的生成原始碼剖析

(1)FeignAutoConfiguration原始碼剖析

FeignAutoConfiguration是feign在整個springcloud的設定類,我拎出這裡面比較核心的程式碼。

@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
    FeignContext context = new FeignContext();
    context.setConfigurations(this.configurations);
    return context;
}

注入了一堆FeignClientSpecification,FeignClientSpecification這玩意就是上文提到的呼叫registerClientConfiguration的時候注入到spring容器中的,一個Feign使用者端的設定一個FeignClientSpecification,所以是個集合,然後封裝到FeignContext中,最後將FeignContext注入到spring容器中。

FeignContext也是很重要的一個東西,我們來分析一下它的原始碼

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
  public FeignContext() {
    super(FeignClientsConfiguration.class, "feign", "feign.client.name");
  }

}

FeignContext繼承了NamedContextFactory,構造的時候,傳入了FeignClientsConfiguration,這個玩意也很重要,別急,我們慢慢來分析它們的作用。

(2)NamedContextFactory原始碼剖析

我先來說結論,NamedContextFactory的作用是用來進行設定隔離的,ribbon和feign的設定隔離都依賴這個抽象類。

何為設定隔離,因為每個Feign使用者端都有可能有自己的設定,從@FeignClient註解的屬性configuration可以看出,所以寫了這個類,用來隔離每個使用者端的設定,這就是為什麼在構造FeignContext傳入一堆FeignClientSpecification的原因,這裡封裝了每個使用者端的設定類。

那是怎麼實現的呢,我拎出來一部分核心的原始碼,不重要的我就忽略了。

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
    implements DisposableBean, ApplicationContextAware {

  private final String propertySourceName;

  private final String propertyName;

  private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
  private Map<String, C> configurations = new ConcurrentHashMap<>();
  
  //父類別 ApplicationContext ,也就是springboot所使用的ApplicationContext
  private ApplicationContext parent;
  // 這個是預設的額設定類
  private Class<?> defaultConfigType;

  public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
      String propertyName) {
    this.defaultConfigType = defaultConfigType;
    this.propertySourceName = propertySourceName;
    this.propertyName = propertyName;
  }

  @Override
  public void setApplicationContext(ApplicationContext parent) throws BeansException {
    this.parent = parent;
  }

  public void setConfigurations(List<C> configurations) {
    for (C client : configurations) {
      this.configurations.put(client.getName(), client);
    }
  }

  public Set<String> getContextNames() {
    return new HashSet<>(this.contexts.keySet());
  }

  protected AnnotationConfigApplicationContext getContext(String name) {
    if (!this.contexts.containsKey(name)) {
      synchronized (this.contexts) {
        if (!this.contexts.containsKey(name)) {
          this.contexts.put(name, createContext(name));
        }
      }
    }
    return this.contexts.get(name);
  }

  protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    if (this.configurations.containsKey(name)) {
      for (Class<?> configuration : this.configurations.get(name)
          .getConfiguration()) {
        context.register(configuration);
      }
    }
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
      if (entry.getKey().startsWith("default.")) {
        for (Class<?> configuration : entry.getValue().getConfiguration()) {
          context.register(configuration);
        }
      }
    }
    context.register(PropertyPlaceholderAutoConfiguration.class,
        this.defaultConfigType);
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
        this.propertySourceName,
        Collections.<String, Object>singletonMap(this.propertyName, name)));
    if (this.parent != null) {
      // Uses Environment from parent as well as beans
      context.setParent(this.parent);
      // jdk11 issue
      // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
      context.setClassLoader(this.parent.getClassLoader());
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
  }
  
  /**
   * Specification with name and configuration.
   */
  public interface Specification {

    String getName();

    Class<?>[] getConfiguration();

  }

}

分析一下每個成員變數的作用:

contexts:一個使用者端一個對應的AnnotationConfigApplicationContext

configurations:一個使用者端一個設定類的封裝,對應到Feign的就是FeignClientSpecification

parent:springboot真正啟動的就是這個ApplicationContext

defaultConfigType:預設的設定類,對應Feign就是構造FeignContext是傳入的FeignClientsConfiguration

分析一下核心的方法:

getContext:這個方法很簡單,就是根據使用者端名稱從contexts獲取對應的AnnotationConfigApplicationContext,獲取不到就去建立一個,然後放入contexts

createContext:就是直接new了一個AnnotationConfigApplicationContext物件,然後按照按照設定的優先順序順序,一步步放入設定類,最後放入parent容器,也就是說每個使用者端對應的容器,都有一個共同的父容器,同時如果每個使用者端對應的容器獲取不到的設定,都會再次從父容器中獲取。這個結論還是很重要的。

其實所謂的設定隔離就是為每個使用者端構建一個AnnotationConfigApplicationContext,然後基於這個ApplicationContext來解析設定類,這樣就實現了設定隔離。

不知道大家有麼有遇到過這個坑,就是在spring cloud環境中,監聽類似ContextRefreshedEvent這種事件的時候,這個事件會無緣無故地觸發很多次,其實就是這個原因就在這,因為spring的事件是有傳播機制的,每個使用者端對應的容器都要進行refresh,refresh完就會發這個事件,然後這個事件就會傳給parent容器,也就是springboot啟動的容器,就會再次觸發,所以如果使用者端很多,那麼就會觸發很多次。解決辦法就是進行唯一性校驗,只能啟動一次就行了。

(3)FeignClientsConfiguration原始碼剖析

說完NamedContextFactory,接下來我們說一下FeignClientsConfiguration的作用。

這是一個預設的設定類,裡面設定了很多bean,這些bean都是生成Feign使用者端動態代理的需要的,我說幾個重要的。

@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
    return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}

  

這個的主要作用是用來解析@FeignClient介面中每個方法使用的springmvc的註解的,這也就是為什麼FeignClient可以識別springmvc註解的原因。

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

  

用來構建動態代理的類,通過這個類的target方法,就能生成Feign動態代理

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

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "feign.hystrix.enabled")
    public Feign.Builder feignHystrixBuilder() {
      return HystrixFeign.builder();
    }

}

這個是FeignClientsConfiguration的內部類是用來整合hystrix的@ConditionalOnProperty(name = "feign.hystrix.enabled")當在組態檔設定了feign.hystrix.enabled=true的時候就開啟了hystrix整合了Feign然後呼叫Feign的介面就有了限流降級的功能其實hystrix整合Feign很簡單就是在構造動態代理的時候加了點東西而已其實不光是hystrixspring cloud alibaba中的sentinel在整合Feign的適合也是按照這個套路來的

(4)構建動態代理的過程原始碼剖析

說完了前置的內容,接下來我們就來看一看動態代理是如何生成的。從上面我們已經知道了,@EnableFeignClinets會掃描出每個加了@FeignClient註解的介面,然後生成對應的BeanDefinition,最後重新生成一個bean class為FeignClientFactoryBean的BeanDefinition,註冊到spring容器。

接下來就會根據BeanDefinition來生成feign使用者端的代理物件了。上面我提到,是通過FeignClientFactoryBean的getObject方法來獲取到代理物件,接下來,我們就來著重分析一下getObject方法的實現。

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

getObject是呼叫getTarget()來獲取代理物件的。

getTarget方法

<T> T getTarget() {
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
      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));
    }
    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();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
        // not load balancing because we have a url,
        // but Spring Cloud LoadBalancer is on the classpath, so unwrap
        client = ((FeignBlockingLoadBalancerClient) 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));
  }

先從ioc容器中獲取到FeignContext,FeignContext裡面封裝了每個Feign的設定起到設定隔離的作用

然後獲取到一個Feign.Builder,預設是在FeignClientsConfiguration中設定的。然後呼叫feign方法。

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

    // @formatter:off
    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));
    // @formatter:on

    //這個是從組態檔中讀取feign的設定
    configureFeign(context, builder);

    return builder;
  }

  

這個方法很簡單,就是從每個FeignClient對應的ioc容器中獲取到對應的元件,填充到 Feign.Builder中,預設都是FeignClientsConfiguration設定的。configureFeign這個方法不用去care它,它是預設從組態檔讀取feign的設定,然後對Feign.Builder進行設定,有可能會覆蓋從每個FeignClient對應的ioc容器中獲取到對應的元件,所以組態檔的優先順序是最高的,但是一般沒人這麼玩,所以就不用care。

上面獲取到的各種元件都是預設的,如果你有需要替換什麼元件,都可以實現,然後通過@FeignClient的configuration設定,就可以替換這些元件。

接下來就是走這段程式碼

if (!StringUtils.hasText(this.url)) {
      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));
}

這段程式碼就是判斷你有沒有指定url,url在哪指定的呢,就是在@FeignClient註解中指定的url屬性,這個屬性是主要是進行feign直連,什麼叫直連,就是不通過註冊中心,直接存取服務提供者,這個url就是設定服務提供者的ip和埠。在springcloud環境下,一般這個是不設定的,因為得從註冊中心發現服務所在的ip和埠列表。所以從這y也可以看出,沒有註冊中心,feign也是能夠跑的,只要指定服務提供者的ip和埠就行。

所以,一般這個url是空的,也就是會進入這段程式碼。其實很簡單,就是設定一個url,name是服務名,也是在@FeignClient設定的。那這段程式碼什麼意思呢,舉個例子來說,假如你的服務名是ServiceA,那麼拼出來就是  ,就是這麼簡單。得到url之後就會走loadBalance方法,傳入一個HardCodedTarget引數,這個引數是封裝了Feign使用者端介面的型別、服務的名稱、還有剛構建的url,接下來進入loadBalance。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
      HardCodedTarget<T> target) {
    Client client = getOptional(context, Client.class);
    if (client != null) {
      builder.client(client);
      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?");
  }

這個方法一上來就從feign使用者端對應的ioc容器中獲取一個Client,但是FeignClientsConfiguration裡面沒有設定Client這個bean,那是從哪來呢?

其實loadBalance這個方法名讓你想到了什麼?當然是負載均衡啦,所以Client需要整合負載均衡的功能,說到負載均衡,當前優先想到ribbon,所以就引入了FeignRibbonClientAutoConfiguration這個設定類,這個類是Feign整合ribbon的設定類,這裡我就先不多說,後面再寫一篇文章來剖析ribbon的原理和feign整合ribbon的原理。當然目前來說,負載均衡元件不止ribbon,還有springcloud自己實現的spring-cloud-starter-loadbalancer這個元件,其實原理都是一樣的。

這裡你就預設獲取到了Client,那麼接下來就放入Feign.Builder中,接下來獲取到Targeter,Targeter是通過FeignAutoConfiguration來設定的,預設是DefaultTargeter,如果整合hystrix就需是HystrixTargeter,當然這裡就是預設的DefaultTargeter。

接下來就會呼叫DefaultTargeter的target方法

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

  

就是直接呼叫Feign.Builder的tartget方法,那麼就進入這個方法

 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方法,這個方法就是將最開始填充到Feign.Builder給封裝起來,構建了一個ReflectiveFeign,然後呼叫ReflectiveFeign的newInstance方法,傳入Target<T> target,也就是前面傳入的HardCodedTarget。

@SuppressWarnings("unchecked")
  @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 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);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }
這個方法我來解釋一下是來幹什麼的,其實很簡單,通過Target拿到介面的型別,然後獲取到所有的方法,遍歷每個方法,處理之後放入methodToHandler中,然後通過InvocationHandlerFactory的create方法,傳入methodToHandler和Target,獲取到一個InvocationHandler,之後通過jdk的動態代理,生成一個代理物件,然後返回回去。InvocationHandler預設是ReflectiveFeign.FeignInvocationHandler,這裡我就不再繼續翻下去了。
走到這裡,我們終於看到了Feign使用者端動態代理的生成,整個構造過程還是很複雜的。這裡我總結一下代理物件生成的過程,每個Feign使用者端都有對應的一個spring容器,用來解析設定類,根據設定從容器獲取到一個Feign.Builder,然後再從容器中獲取每個元件,填充到Feign.Builder中,最後通過Feign.Builder的build方法來構造動態代理,構造的過程其實是屬於feign包底下的。

三、總結

本文主要是講述了,在SpringCloud環境下,OpenFeign對於Feign使用者端動態代理的的構造過程。最開始講解了@EnableFeignClinets註解的作用開始,隨後剖析了FeignAutoConfiguration和FeignClientsConfiguration設定類,同時提到了Feign對每個使用者端都進行了設定的隔離,最後通過剖析FeignClientFactoryBean的getObject方法,來一步一步屢清楚動態代理的構建過程。

至於OpenFeign是如何跟ribbon整合的,以及其他SpringCloud元件的原理,我會單獨再寫幾篇文章來剖析。

最後畫一張圖,來總結一下

 

 

 

以上就是本篇文章的全部內容,如果你有什麼不懂或者想要交流的地方,歡迎關注我的個人的微信公眾號 三友的java日記  聯絡我,我們下篇文章再見。

如果覺得這篇文章對你有所幫助,還請幫忙點贊、在看、轉發一下,碼字不易,非常感謝!

往期熱門文章推薦