spring-cloud-openfeign 源碼解析

spring-cloud-openfeign 源碼解析:

  本文主要針對 spring-cloud-starter-openfeign 的 2.2.3.RELEASE 版本進行源碼的解析。

  對於未接觸過 Feign的小夥伴可以參考 https://www.cnblogs.com/wuzhenzhao/p/9472607.html 進行一些基礎知識的瞭解。

@EnableFeignClients

  想要集成 Feign 客戶端,需要我們通過註解 @EnableFeignClients 來開啓。這個註解開啓了FeignClient的解析過程。這個註解的聲明如下,它用到了一個@Import註解,我們知道Import是用來導入一個配置類的,接下來去看一下FeignClientsRegistrar的定義

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

  FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar,它是一個動態注入bean的接口,Spring Boot啓動的時候,會去調用這個類中的registerBeanDefinitions來實現動態Bean的裝載。它的作用類似於ImportSelector。

  對於動態注入不清楚的小夥伴可以參考 : https://www.cnblogs.com/wuzhenzhao/p/9151673.html 。

  然後就會進入  FeignClientsRegistrar# registerBeanDefinitions  。registerDefaultConfiguration 方法內部從 SpringBoot 啓動類上檢查是否有@EnableFeignClients, 有該註解的話, 則完成 Feign 框架相關的一些配置內容註冊registerFeignClients 方法內部從 classpath 中, 掃描獲得 @FeignClient 修飾的類, 將類的內容解析爲 BeanDefinition , 最終通過調用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 將解析處理過的 FeignClientBeanDeifinition 添加到 spring 容器中.

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
                  BeanDefinitionRegistry registry) {
  //註冊@EnableFeignClients中定義defaultConfiguration屬性下的類,包裝成FeignClientSpecification,註冊到Spring容器。
  //在@FeignClient中有一個屬性:configuration,這個屬性是表示各個FeignClient自定義的配置類,後面也會通過調用registerClientConfiguration方法來註冊成FeignClientSpecification到容器。
  //所以,這裏可以完全理解在@EnableFeignClients中配置的是做爲兜底的配置,在各個@FeignClient配置的就是自定義的情況。
  registerDefaultConfiguration(metadata, registry);
  registerFeignClients(metadata, registry);
}

  這裏面需要重點分析的就是 registerFeignClients 方法,這個方法主要是掃描類路徑下所有的@FeignClient註解,然後進行動態Bean的注入。它最終會調用 registerFeignClient 方法。

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"));
                    // 註冊Feign 客戶端
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
}

   registerFeignClient  在這個方法中,就是去組裝BeanDefinition,也就是Bean的定義,然後註冊到Spring IOC容器。

private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        // 省略代碼.....
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                new String[] { alias });
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

  我們關注一下,BeanDefinitionBuilder是用來構建一個BeanDefinition的,它是通過 genericBeanDefinition 來構建的,並且傳入了一個FeignClientFactoryBean的類,代碼如下。

/**
* Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}.
* @param beanClass the {@code Class} of the bean that the definition is being created for
*/
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
        BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
        builder.beanDefinition.setBeanClass(beanClass);
        return builder;
}

  我們可以發現,FeignClient被動態註冊成了一個FactoryBean.

  Spring Cloud FengnClient實際上是利用Spring的代理工廠來生成代理類,所以在這裏地方纔會把所有的FeignClient的BeanDefinition設置爲FeignClientFactoryBean類型,而FeignClientFactoryBean繼承自FactoryBean,它是一個工廠Bean。在Spring中,FactoryBean是一個工廠Bean,用來創建代理Bean。工廠 Bean 是一種特殊的 Bean, 對於 Bean 的消費者來說, 他邏輯上是感知不到這個 Bean 是普通的 Bean 還是工廠 Bean, 只是按照正常的獲取 Bean 方式去調用, 但工廠bean 最後返回的實例不是工廠Bean 本身, 而是執行工廠 Bean 的 getObject 邏輯返回的示例。

  簡單來說,FeignClient標註的這個接口,會通過FeignClientFactoryBean.getObject()這個方法獲得一個代理對象。

  上述流程主要可以用下圖表示:

FeignClientFactoryBean.getObject:

  getObject調用的是getTarget方法,它從applicationContext取出FeignContext,FeignContext繼承了NamedContextFactory,它是用來統一維護feign中各個feign客戶端相互隔離的上下文。

  FeignContext註冊到容器是在FeignAutoConfiguration上完成的。

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

  在初始化FeignContext時,會把configurations在容器中放入FeignContext中。configurations 的來源就是在前面registerFeignClients方法中將@FeignClient的配置 configuration。

  接着,構建feign.builder,在構建時會向FeignContext獲取配置的Encoder,Decoder等各種信息。FeignContext在上文中已經提到會爲每個Feign客戶端分配了一個容器,它們的父容器就是spring容器

  配置完Feign.Builder之後,再判斷是否需要LoadBalance,如果需要,則通過LoadBalance的方法來設置。實際上他們最終調用的是Target.target()方法。

@Override
public Object getObject() throws Exception {
    return getTarget();
}
<T> T getTarget() {
        //實例化Feign上下文對象FeignContext
        FeignContext context = this.applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);//構建Builder對象
    //如果url爲空,則走負載均衡,生成有負載均衡功能的代理類
        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,則生成默認的代理類
        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));
}

  loadBalance :生成具備負載均衡能力的feign客戶端,爲feign客戶端構建起綁定負載均衡客戶端

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

  Client client = (Client)this.getOptional(context, Client.class); 從上下文中獲取一個 Client,默認是LoadBalancerFeignClient。它是在FeignRibbonClientAutoConfiguration這個自動裝配類中,通過Import實現的

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
        OkHttpFeignLoadBalancedConfiguration.class,
        DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
  .....
}

  這裏的通過 DefaultFeignLoadBalancedConfiguration 注入客戶端 Client 的實現

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

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

}

  接下去進入  targeter.target(this, builder, context, target) ,攜帶着構建好的這些對象去創建代理實例 ,這裏有兩個實現  HystrixTargeter 、DefaultTargeter 很顯然,我們沒有配置 Hystrix ,這裏會走 DefaultTargeter

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.Feign.Builder#target(feign.Target<T>)

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

  最終會調用  ReflectiveFeign.newInstance

  這個方法是用來創建一個動態代理的方法,在生成動態代理之前,會根據Contract協議(協議解析規則,解析接口類的註解信息,解析成內部的MethodHandler的處理方式。

  從實現的代碼中可以看到熟悉的Proxy.newProxyInstance方法產生代理類。而這裏需要對每個定義的接口方法進行特定的處理實現,所以這裏會出現一個MethodHandler的概念,就是對應方法級別的InvocationHandler。

public <T> T newInstance(Target<T> target) {
    // 解析接口註解信息
    //根據接口類和Contract協議解析方式,解析接口類上的方法和註解,轉換成內部的MethodHandler處理方式
    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);
  // 基於Proxy.newProxyInstance 爲接口類創建動態實現,將所有的請求轉換給InvocationHandler 處理。
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}

  targetToHandlersByName.apply(target) :根據Contract協議規則,解析接口類的註解信息,解析成內部表現:targetToHandlersByName.apply(target);會解析接口方法上的註解,從而解析出方法粒度的特定的配置信息,然後生產一個SynchronousMethodHandler 然後需要維護一個<method,MethodHandler>的map,放入InvocationHandler的實現FeignInvocationHandler中。

public Map<String, MethodHandler> apply(Target target) {
      List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          buildTemplate =
              new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else {
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
        }
        if (md.isIgnored()) {
          result.put(md.configKey(), args -> {
            throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
          });
        } else {
          result.put(md.configKey(),
              factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
        }
      }
      return result;
}

  SpringMvcContract :當前Spring Cloud 微服務解決方案中,爲了降低學習成本,採用了Spring MVC的部分註解來完成 請求協議解析,也就是說 ,寫客戶端請求接口和像寫服務端代碼一樣:客戶端和服務端可以通過SDK的方式進行約定,客戶端只需要引入服務端發佈的SDK API,就可以使用面向接口的編碼方式對接服務。

OpenFeign調用過程 :

  在前面的分析中,我們知道OpenFeign最終返回的是一個 ReflectiveFeign.FeignInvocationHandler 的對象。那麼當客戶端發起請求時,會進入到 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();
      }
      // 利用分發器篩選方法,找到對應的handler 進行處理
      return dispatch.get(method).invoke(args);
}

  而接着,在invoke方法中,會調用 this.dispatch.get(method)).invoke(args) 。this.dispatch.get(method) 會返回一個SynchronousMethodHandler,進行攔截處理。這個方法會根據參數生成完成的RequestTemplate對象,這個對象是Http請求的模版,代碼如下。

@Override
  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) {
        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;
      }
    }
}

  經過上述的代碼,我們已經將restTemplate拼裝完成,上面的代碼中有一個 executeAndDecode() 方法,該方法通過RequestTemplate生成Request請求對象,然後利用Http Client獲取response,來獲取響應信息。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    //轉化爲Http請求報文
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      //發起遠程通信
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      //獲取返回結果

      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
    // .......
}

  經過上面的分析,這裏的 client.execute  的 client 的類型是LoadBalancerFeignClient

  這裏就很自然的進入 LoadBalancerFeignClient#execute

public Response execute(Request request, Request.Options options) throws IOException {
        try {
            URI asUri = URI.create(request.url());
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);
            FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                    this.delegate, request, uriWithoutHost);

            IClientConfig requestConfig = getClientConfig(options, clientName);
            return lbClient(clientName)
                    .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
        }
        catch (ClientException e) {
            IOException io = findIOException(e);
            if (io != null) {
                throw io;
            }
            throw new RuntimeException(e);
        }
}

  其實這個execute裏面得流程就是 Ribbon 的那一套。我們可以簡單的看一下。首先是構造URI,構造RibbonRequest,選擇 LoadBalance,發起調用。

  來看一下lbClient 選擇負載均衡器的時候做了什麼 

public FeignLoadBalancer create(String clientName) {
        FeignLoadBalancer client = this.cache.get(clientName);
        if (client != null) {
            return client;
        }
        IClientConfig config = this.factory.getClientConfig(clientName);
        ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
        ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
                ServerIntrospector.class);
        client = this.loadBalancedRetryFactory != null
                ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                        this.loadBalancedRetryFactory)
                : new FeignLoadBalancer(lb, config, serverIntrospector);
        this.cache.put(clientName, client);
        return client;
}

  可以得出的結論就是 this.factory.getLoadBalancer(clientName)  跟Ribbon 源碼裏的獲取方式一樣,無疑這裏獲取的就是默認的  ZoneAwareLoadBalancer。然後包裝成一個 FeignLoadBalancer 進行返回

  既然負載均衡器選擇完了,那麼一定還有個地方通過該負載去選擇一個服務,接着往下看:

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }
        
}

  上面這段代碼就是通過獲取到的負載進行執行請求,但是這個時候  服務還沒有選擇,我們跟進去 submit 請求看一看究竟:

public Observable<T> submit(final ServerOperation<T> operation) {
        final ExecutionInfoContext context = new ExecutionInfoContext();
        // .........
        Observable<T> o = 
                (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    //........
                });
            
       // .......
}

  可以看到這裏有個 selectServer的方法 ,跟進去:

public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
        String host = null;
        int port = -1;
        if (original != null) {
            host = original.getHost();
        }
        if (original != null) {
            Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);        
            port = schemeAndPort.second();
        }

        // Various Supported Cases
        // The loadbalancer to use and the instances it has is based on how it was registered
        // In each of these cases, the client might come in using Full Url or Partial URL
        ILoadBalancer lb = getLoadBalancer();
        if (host == null) {
            // ............
        } else {
            // ...........if (shouldInterpretAsVip) {
                Server svc = lb.chooseServer(loadBalancerKey);
                if (svc != null){
                    host = svc.getHost();
                    if (host == null){
                        throw new ClientException(ClientException.ErrorType.GENERAL,
                                "Invalid Server for :" + svc);
                    }
                    logger.debug("using LB returned Server: {} for request: {}", svc, original);
                    return svc;
                } else {
                    // just fall back as real DNS
                    logger.debug("{}:{} assumed to be a valid VIP address or exists in the DNS", host, port);
                }
            } else {
                // consult LB to obtain vipAddress backed instance given full URL
                //Full URL execute request - where url!=vipAddress
                logger.debug("Using full URL passed in by caller (not using load balancer): {}", original);
            }
        }
        // ..........

        return new Server(host, port);
    }

  可以看到的是這裏獲取到了之前構造好的 ZoneAwareLoadBalancer 然後調用  chooseServer 方法獲取server ,這個是跟Ribbon 中是一樣的流程,這裏就不贅述了。

  獲取到了server 後,會回調先前  executeWithLoadBalancer 方法裏構造的  ServerOperation 的 call 方法:

return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();

  然後會執行 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 進行最後的調用,實際上這裏走的是 FeignLoadBalancer#execute

@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
            throws IOException {
        Request.Options options;
        if (configOverride != null) {
            RibbonProperties override = RibbonProperties.from(configOverride);
            options = new Request.Options(override.connectTimeout(this.connectTimeout),
                    override.readTimeout(this.readTimeout));
        }
        else {
            options = new Request.Options(this.connectTimeout, this.readTimeout);
        }
        Response response = request.client().execute(request.toRequest(), options);
        return new RibbonResponse(request.getUri(), response);
}

  而這裏調用的   request.client().execute(request.toRequest(), options) 則是 DefaultFeignLoadBalancedConfiguration 注入的 LoadBalancerFeignClient ,在構造 LoadBalancerFeignClient 的時候 ,傳遞了個 feign.Client.Default ,然後利用 feign.Client.Default 構造了一個  RibbonRequest。

  所以這裏走 feign.Client.Default#execute :

@Override
public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
}

  利用 JDK  提供的 HttpURLConnection 發起遠程的 HTTP通訊。至此發起請求的流程就完成了。下面附上一張這個過程的流程圖,對於Ribbon的調用過程請參考 :Ribbon 源碼分析

OpenFeign  Configuration :

  針對 feign 的 Configuration ,官方給我們提供了很多的個性化配置,具體可以參考 org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration

public static class FeignClientConfiguration {
        // 日誌
        private Logger.Level loggerLevel;
        // 連接超時
        private Integer connectTimeout;
     
        private Integer readTimeout;
       //重試
        private Class<Retryer> retryer;
        //解碼
        private Class<ErrorDecoder> errorDecoder;

        private List<Class<RequestInterceptor>> requestInterceptors;
        // 編碼
        private Boolean decode404;

        private Class<Decoder> decoder;

        private Class<Encoder> encoder;
        // 解析
        private Class<Contract> contract;

        private ExceptionPropagationPolicy exceptionPropagationPolicy;
}

  這裏舉個簡單的例子,以Logger 爲例。我們想爲每個不同的 FeignClient 設置日誌級別。

1.添加配置類:

@Configuration
public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

2.配置日誌級別 ,logging.level + FeignClient 包的全路徑。

logging.level.com.wuzz.FeignClientService: DEBUG

  就這樣就配置完成了。重啓服務就可以看到效果。

  更多配置請參考官網

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章