SpringCloud Feign 源码分析

一:@FeignClient 注解分析:

首先来分析一下@FeignClient这个注解,点进去这个注解看下,代码如下:

/**
 * Annotation for interfaces declaring that a REST client with that interface should be
 * created (e.g. for autowiring into another component). If ribbon is available it will be
 * used to load balance the backend requests, and the load balancer can be configured
 * using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
 *
 * @author Spencer Gibb
 * @author Venil Noronha
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

	/**
	 * The name of the service with optional protocol prefix. Synonym for {@link #name()
	 * name}. A name must be specified for all clients, whether or not a url is provided.
	 * Can be specified as property key, eg: ${propertyKey}.
	 */
	@AliasFor("name")
	String value() default "";

	/**
	 * The service id with optional protocol prefix. Synonym for {@link #value() value}.
	 *
	 * @deprecated use {@link #name() name} instead
	 */
	@Deprecated
	String serviceId() default "";

	/**
	 * This will be used as the bean name instead of name if present, but will not be used as a service id.
	 */
	String contextId() default "";

	/**
	 * The service id with optional protocol prefix. Synonym for {@link #value() value}.
	 */
	@AliasFor("value")
	String name() default "";
	
	/**
	 * Sets the <code>@Qualifier</code> value for the feign client.
	 */
	String qualifier() default "";

	/**
	 * An absolute URL or resolvable hostname (the protocol is optional).
	 */
	String url() default "";

	/**
	 * Whether 404s should be decoded instead of throwing FeignExceptions
	 */
	boolean decode404() default false;

	/**
	 * A custom <code>@Configuration</code> for the feign client. Can contain override
	 * <code>@Bean</code> definition for the pieces that make up the client, for instance
	 * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
	 *
	 * @see FeignClientsConfiguration for the defaults
	 */
	Class<?>[] configuration() default {};

	/**
	 * Fallback class for the specified Feign client interface. The fallback class must
	 * implement the interface annotated by this annotation and be a valid spring bean.
	 */
	Class<?> fallback() default void.class;

	/**
	 * Define a fallback factory for the specified Feign client interface. The fallback
	 * factory must produce instances of fallback classes that implement the interface
	 * annotated by {@link FeignClient}. The fallback factory must be a valid spring
	 * bean.
	 *
	 * @see feign.hystrix.FallbackFactory for details.
	 */
	Class<?> fallbackFactory() default void.class;

	/**
	 * Path prefix to be used by all method-level mappings. Can be used with or without
	 * <code>@RibbonClient</code>.
	 */
	String path() default "";

	/**
	 * Whether to mark the feign proxy as a primary bean. Defaults to true.
	 */
	boolean primary() default true;

}

@Target(ElementType.TYPE),表示FeignClient注解作用目标在接口上。
@Retention(RetentionPolicy.RUNTIME),表示该注解会在Class字节码文件中存在,在运行时可以通过反射获取到。
@Documented,表示该注解将被包含在Javadoc中

简单的介绍下各个属性。
value(), name()都是一样的,都是定义的serviceId。
uri()直接写硬编码的地址,一般用来调试用。
decode404()针对返回结果404被解码或者抛异常。
configuration()为feign的配置类,默认的配置类FeignClientsConfiguration
fallback()为相关的熔断类。

二:默认配置类FeignClientsConfiguration源码分析

接下来看下FeignClientsConfiguration。需要注意一下带有@Bean 和 @ConditionalOnMissingBean这两个注解的方法。代码如下:

package org.springframework.cloud.openfeign;
/**
 * @author Dave Syer
 * @author Venil Noronha
 */
@Configuration
public class FeignClientsConfiguration {

	@Autowired
	private ObjectFactory<HttpMessageConverters> messageConverters;

	@Autowired(required = false)
	private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

	@Autowired(required = false)
	private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

	@Autowired(required = false)
	private Logger logger;

	@Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder() {
		return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
	}

	@Bean
	@ConditionalOnMissingBean
	public Encoder feignEncoder() {
		return new SpringEncoder(this.messageConverters);
	}

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

	@Bean
	public FormattingConversionService feignConversionService() {
		FormattingConversionService conversionService = new DefaultFormattingConversionService();
		for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
			feignFormatterRegistrar.registerFormatters(conversionService);
		}
		return conversionService;
	}

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

	@Bean
	@ConditionalOnMissingBean
	public Retryer feignRetryer() {
		return Retryer.NEVER_RETRY;
	}

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

	@Bean
	@ConditionalOnMissingBean(FeignLoggerFactory.class)
	public FeignLoggerFactory feignLoggerFactory() {
		return new DefaultFeignLoggerFactory(logger);
	}
}

可以知道这个配置类注入了很多个bean,@ConditionalOnMissingBean注解表示 如果没有注入该类的Bean,那么就会默认注入一个Bean。上一篇文章中曾写过的配置类 FeignConfig,代码如下:

package com.example.eurekafeignclient.config;

//注入Retryer类的实例,这样在远程调用失败后,feign会进行重试
@Configuration
public class FeignConfig {

    public Retryer feignRetryer(){
        //Feign 默认的配置在请求失败后,重试次数为0,即不重试。
        //重试间隔 为100毫秒,最大重试时间为1秒,重试次数为5次
        //return new Retryer.Default();
        return new Retryer.Default(100,SECONDS.toMillis(1),5);
    }
}

重新实例化一个Retryer的Bean去覆盖掉默认的Retryer bean,表明FeignClientsConfiguration支持自定义配置。我们只需要重写FeignClientsConfiguration类中的Bean去覆盖默认的配置Bean,就可以达到自定义配置的目的。

二:工作原理源码分析

接着我们来看下在启动类上的@EnableFeignClients 注解,代码如下:

package org.springframework.cloud.openfeign;

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

	/**
	 * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
	 * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
	 * {@code @ComponentScan(basePackages="org.my.pkg")}.
	 * @return the array of 'basePackages'.
	 */
	String[] value() default {};

	/**
	 * Base packages to scan for annotated components.
	 * <p>
	 * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
	 * <p>
	 * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
	 * package names.
	 *
	 * @return the array of 'basePackages'.
	 */
	String[] basePackages() default {};

	/**
	 * Type-safe alternative to {@link #basePackages()} for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 *
	 * @return the array of 'basePackageClasses'.
	 */
	Class<?>[] basePackageClasses() default {};

	/**
	 * A custom <code>@Configuration</code> for all feign clients. Can contain override
	 * <code>@Bean</code> definition for the pieces that make up the client, for instance
	 * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
	 *
	 * @see FeignClientsConfiguration for the defaults
	 */
	Class<?>[] defaultConfiguration() default {};

	/**
	 * List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
	 * @return list of FeignClient classes
	 */
	Class<?>[] clients() default {};
}

@Import(FeignClientsRegistrar.class),注入 FeignClientsRegistrar 的Bean。
跟进这个注解,代码如下:

public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
        ResourceLoaderAware, BeanClassLoaderAware {

    // patterned after Spring Integration IntegrationComponentScanRegistrar
    // and RibbonClientsConfigurationRegistgrar
    private final Logger logger = LoggerFactory.getLogger(FeignClientsRegistrar.class);
    private ResourceLoader resourceLoader;

    private ClassLoader classLoader;

    public FeignClientsRegistrar() {
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
            
        //1、扫描是否有EnableFeignClients注解,然后加载配置( 扫描EnableFeignClients标签里配置的信息,注册到beanDefinitionNames中。)
        registerDefaultConfiguration(metadata, registry);
       
        //2、扫描所有带有@FeignClient注解的类 ,然后注册
        registerFeignClients(metadata, registry);
    }
    //...
}

从上面代码可以发现主要做了 扫描 是否有EnableFeignClients注解并注册 到Spring容器中和扫描
扫描所有带有@FeignClient注解的类并注册到Spring容器中两件事情。

2.1 扫描是否有EnableFeignClients注解,并注册到Spring容器中

跟进registerDefaultConfiguration(metadata, registry); 这个方法中,

//扫描是否有EnableFeignClients注解,然后加载配置
private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
			
		//读取启动类上面@EnableFeignClients注解中声明feign相关配置类,
		//默认name为default,一般情况下无需配置。用默认的FeignAutoConfiguration即可。
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
		
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}

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

上面将bean配置类包装成FeignClientSpecification,注入到容器。该对象非常重要,包含FeignClient需要的重试策略,超时策略,日志等配置,如果某个服务没有设置,则读取默认的配置。
至此,扫描是否有EnableFeignClients注解,并注册到Spring容器中

2.2 扫描所有带有@FeignClient注解的类,将服务接口注册到Spring容器中

点进去registerFeignClients(metadata, registry); 这个方法

public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
       
        //省略代码...根据basePackages找到包下所有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");

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

                    String name = getClientName(attributes);
                  
                  
                    //这里是第二处调用。这里主要是将扫描的目录下,每个项目的配置类加载的容器当中。
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));       
                   
                    //重点分析这个
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }
  

可以看到上面又调用了registerClientConfiguration注册配置的方法,这里是第二处调用。这里主要是将扫描的目录下,每个项目的配置类加载的容器当中。
注册到容器中,什么时候会用到呢?具体又如何使用呢?别着急,后面会有介绍。

我们先会回到继续主流程,继续看注册feignClient的方法,跟进registerFeignClient(registry, annotationMetadata, attributes);

  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 = 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 });
        //将使用了@FeignClient注解的接口全部注入到Spring容器中。
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

跟进 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

至此,扫描所有带有@FeignClient注解的类,将服务接口注册到Spring容器中。

三:Feign是如何进行服务调用的?

通过jdk的代理,当请求Feign Client的方法时会被拦截,代码在ReflectiveFeign类,代码如下:

/**
   * creates an api binding to the {@code target}. As this invokes reflection, care should be taken
   * to cache the result.
   */
  @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>();

    //将要被代理的方法全部在methodToHandler中;
    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的动态代理为指定的Feign接口生成代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

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

在SynchronousMethodHandler类进行拦截处理,当被FeignClient的方法被拦截会根据参数生成RequestTemplate对象,该对象就是http请求的模板,代码如下:

public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } 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;
      }
    }
  }

其中有个executeAndDecode()方法,该方法是通RequestTemplate生成Request请求对象,然后用Http Client 获取response,即通过 Http Client 进行 Http 请求来获取响应。代码如下:

Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);
    ...//省略代码
    response = client.execute(request, options);
    ...//省略代码
}

在这里插入图片描述
在这里插入图片描述

四:Feign是如何实现负载均衡的?

由FeignRibbonClientAutoConfiguration自动配置类可知,向容器注入的是LoadBalancerFeignClient(这个类与Ribbon研究中的LoadBalancerClient类在功能上有相似性),即负载均衡客户端。LoadBalancerFeignClient 类代码如下:

package org.springframework.cloud.openfeign.ribbon;

/**
 * @author Dave Syer
 *
 */
public class LoadBalancerFeignClient implements Client {

	static final Request.Options DEFAULT_OPTIONS = new Request.Options();

	private final Client delegate;
	private CachingSpringLoadBalancerFactory lbClientFactory;
	private SpringClientFactory clientFactory;

	public LoadBalancerFeignClient(Client delegate,
								   CachingSpringLoadBalancerFactory lbClientFactory,
								   SpringClientFactory clientFactory) {
		this.delegate = delegate;
		this.lbClientFactory = lbClientFactory;
		this.clientFactory = clientFactory;
	}

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

	IClientConfig getClientConfig(Request.Options options, String clientName) {
		IClientConfig requestConfig;
		if (options == DEFAULT_OPTIONS) {
			requestConfig = this.clientFactory.getClientConfig(clientName);
		} else {
			requestConfig = new FeignOptionsClientConfig(options);
		}
		return requestConfig;
	}

	protected IOException findIOException(Throwable t) {
		if (t == null) {
			return null;
		}
		if (t instanceof IOException) {
			return (IOException) t;
		}
		return findIOException(t.getCause());
	}

	public Client getDelegate() {
		return this.delegate;
	}

	static URI cleanUrl(String originalUrl, String host) {
		String newUrl = originalUrl;
		if(originalUrl.startsWith("https://")) {
			newUrl = originalUrl.substring(0, 8) + originalUrl.substring(8 + host.length());
		} else if(originalUrl.startsWith("http")) {
			newUrl = originalUrl.substring(0, 7) + originalUrl.substring(7 + host.length());
		}
		StringBuffer buffer = new StringBuffer(newUrl);
		if((newUrl.startsWith("https://") && newUrl.length() == 8) ||
				(newUrl.startsWith("http://") && newUrl.length() == 7)) {
			buffer.append("/");
		}
		return URI.create(buffer.toString());
	}

	private FeignLoadBalancer lbClient(String clientName) {
		return this.lbClientFactory.create(clientName);
	}

	static class FeignOptionsClientConfig extends DefaultClientConfigImpl {

		public FeignOptionsClientConfig(Request.Options options) {
			setProperty(CommonClientConfigKey.ConnectTimeout,
					options.connectTimeoutMillis());
			setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
		}

		@Override
		public void loadProperties(String clientName) {

		}

		@Override
		public void loadDefaultValues() {

		}

	}
}

具体看execute 方法,代码如下:

@Override
    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);
            
             // 封装 Ribbon 请求信息 
            FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                    this.delegate, request, uriWithoutHost);
                    
            // 获取 Ribbon 请求相关配置信息
            IClientConfig requestConfig = getClientConfig(options, clientName);
            
            // 执行操作(根据封装的 RibbonRequest (ribbon请求) 和 IClientConfig (ribbon请求配置属性) 发起请求。)
            return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
                    requestConfig).toResponse();
        }
        catch (ClientException e) {
            IOException io = findIOException(e);
            if (io != null) {
                throw io;
            }
            throw new RuntimeException(e);
        }
    }

根据 RibbonRequest 和 IClientConfig 发起请求。
LoadBalancerFeignClient#execute return 执行的代码如下:

lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();	

该代码中,包括两个执行逻辑**(重点)**

**//得到 FeignLoadBalancer 客户端负载均衡实例对象
LoadBalancerFeignClient # lbClient(clientName)  
// 实现负载均衡
FeignLoadBalancer # executeWithLoadBalancer** 

我们分别来看看这两个执行逻辑。

1.聚焦LoadBalancerFeignClient#lbClient

private FeignLoadBalancer lbClient(String clientName) {
		return this.lbClientFactory.create(clientName);
	}

LoadBalancerFeignClient # lbClient 逻辑:通过 clientName 获取 FeignLoadBalancer 对象。

而 FeignLoadBalancer 对象的获取是从 CachingSpringLoadBalancerFactory#create 中获取的。
跟进 create 这个方法:

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 = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
			loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
		this.cache.put(clientName, client);
		return client;
	}

CachingSpringLoadBalancerFactory 是一个缓存工厂,缓存了 clientName 对应 FeignLoadBalancer 关系结构。

通过代码,我们可以知道 FeignLoadBalancer 是实现 Feign 客户端负载均衡调用的一个类, FeignLoadBalancer 对 Ribbon ILoadBalancer 进行了封装,将 RIbbon 的数据 如:ILoadBalancer, IClientConfig 直接以成员变量的形式存入 FeignLoadBalancer 中。

所有 lbClient(clientName) 得到 FeignLoadBalancer 客户端负载均衡实例对象之后,就是开始调用 FeignLoadBalancer#executeWithLoadBalancer。

2.聚焦 FeignLoadBalancer # executeWithLoadBalancer
点进去该方法:

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

该方法有两个主要逻辑:

LoadBalancerCommand#submit
该方法实现客户端负载均衡,根据 IRule(官方默认实现: ZoneAvoidanceRule) 从 ILoadBalancer(Eureka服务实例管理,默认:DynamicServerListLoadBalancer) 中获取一个Server实例对象。
this.execute(requestForServer, requestConfig)
根据 LoadBalancerCommand#submit 获取到的 Server ,调用 FeignLoadBalancer#execute 发起服务调用请求,并返回结果。

点击command.submit()的submit()方法,到达LoadBalancerCommand 类
submit()部分代码如下:

  // Use the load balancer
        Observable<T> o = 
                (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    @Override
                    // Called for each server being selected
                    public Observable<T> call(Server server) {
                        context.setServer(server);
                        final ServerStats stats = loadBalancerContext.getServerStats(server);
                        
                        // Called for each attempt and retry
                        Observable<T> o = Observable
                                .just(server)
                                .concatMap(new Func1<Server, Observable<T>>() {
                                    @Override
                                    public Observable<T> call(final Server server) {
                                        context.incAttemptCount();
                                        loadBalancerContext.noteOpenConnection(stats);

上述代码中有个selectServe(),该方法是选择服务的进行负载均衡的方法

 /**
     * Return an Observable that either emits only the single requested server
     * or queries the load balancer for the next server on each subscription
     */
    private Observable<Server> selectServer() {
        return Observable.create(new OnSubscribe<Server>() {
            @Override
            public void call(Subscriber<? super Server> next) {
                try {
                    Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                    next.onNext(server);
                    next.onCompleted();
                } catch (Exception e) {
                    next.onError(e);
                }
            }
        });
    }

3.负载均衡总结
1、LoadBalancerFeignClient#execute() 是整个 Feign Client 的执行入口
2、LoadBalancerFeignClient#execute() 方法主线逻辑:

封装 RibbonReuqest 请求对象,从容器中获取 Ribbon 请求配置参数信息 IConfigClient,根据 RibbonRequest 和 IConfigClient 获取FeignLoadBalancer 负载均衡实例对象。
3、FeignLoadBalancer 根据 Ribbon 的 IRule 规则从 Iloadbalancer 获取一个服务实例 server。
4、FeignLoadBalancer#execute 根据获取到的服务实例 Server 发起请求调用。然后返回。
5.FeignLoadBalancer 其实就是 Feign 对 Ribbon 的一层封装,实际调用的就是 Ribbon 的负载均衡逻辑,包括 IRule 等 进行服务实例的获取。
所以说,Feign 最后还是走 Riboon的

五:总结

总到来说,Feign的源码实现的过程如下:
1.首先通过@EnableFeignCleints注解开启FeignCleint,根据Feign的规则实现接口,并加@FeignCleint注解
2.程序启动后,会进行包扫描,扫描所有的@ FeignCleint的注解的类,并将这些信息注入到ioc容器中。
3.当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate,
4.跟进RequesTemplate再生成Http请求的Request对象。
5.Request 对象交给 Feign Client 去处理。
5.最后Feign Client被封装到LoadBalancerFeignClient类,这个类是 Feign 对 Ribbon 的一层封装,做到了负载均衡。

参考资料:
https://blog.csdn.net/qq_23202687/article/details/94408008
https://cloud.tencent.com/developer/article/1009212

发布了33 篇原创文章 · 获赞 42 · 访问量 3161

猜你喜欢

转载自blog.csdn.net/weixin_40991408/article/details/103869100
今日推荐