SpringCloud系列——OpenFeign服务接口调用(四)

577 阅读12分钟

OpenFeign是什么?

Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.

feign是一个声明式的web服务客户端。它使得编写web服务客户端更加容易。要使用 feign 则需要创建一个接口并给接口添加上注解即可。
它具有可插入式的注解支持,包含feign注解和JAX-RS注解。feign也支持可插入的编码和解码。Spring Cloud增加了对Spring MVC注释的支持,并支持使用Spring Web中默认使用的HttpMessageConverters。当使用 Feign 时,Spring Cloud 整合了 Eureka,Spring Cloud CircuitBreaker(断路器)以及Spring Cloud LoadBalancer(负载均衡)以提供负载均衡的http client。

说白了, openFeign就是个针对服务端映射(controller)的客户端工具, 主要目的是让 客户端 能够以类似 调用service的方式 调用到 服务端 的 controller

Feign和OpenFeign的区别

FeignopenFiegn
Feign是SpringCloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务OpenFeign 是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等。OpenFeign 的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

怎么用?

openFeign 也是针对客户端设计的, 所以代码一般卸载客户端上

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
  <dependency>
    <groupId>com.zhazha</groupId>
    <artifactId>cloud-api-common</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <!--一般基础通用配置-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>
server:
  port: 80
spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-order-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
#        register-enabled: true
#        watch:
#          enabled: true
        username: nacos
        password: nacos
management:
  endpoints:
    web:
      exposure:
        ## yml文件中存在特殊字符,必须用单引号包含,否则启动报错
        include: '*'
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;


@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class OrderFeignMain80 {

	public static void main(String[] args) throws Exception {
		SpringApplication.run(OrderFeignMain80.class, args);
	}

}

import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.utils.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// 区分大小写
// 这里需要和服务提供方的 spring.application.name 的值相同
@FeignClient(value = "cloud-payment-service")
public interface PaymentFeignService {

    @GetMapping("/payment/get/{id}")
    CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
    
    @PostMapping(value = "create")
    CommonResult create(@RequestBody Payment payment);

}

import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.service.PaymentFeignService;
import com.zhazha.springcloud.utils.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        return paymentFeignService.getPaymentById(id);
    }

}

注意:CommonResult create(@RequestBody Payment payment); openFeign默认的传参方式就是JSON传参(@RequestBody),因此定义接口的时候可以不用@RequestBody注解标注,不过为了规范,一般都填上。

注意:因为 openFeign默认使用JSON传参方式,如果需要使用表单传参方式则改为@SpringQueryMap CommonResult create(@SpringQueryMap Payment payment);

OpenFeign超时设置

openFeign底层默认超时判定时间是 1秒 , 如果超出 1秒就会抛出异常

所以在有必要的情况下, 可以自行修改超时时间

feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

但是如果需要调用涉及到多个openFeign接口的调用呢?

比如下图的情况:

image.png

很明显,ServiceA ServiceB 能够通过,但 ServiceC 不能够通过

此时我们可以单独给 serviceC 设置超时时间:

feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic
      cloud-order-service:
        connectTimeout: 50000
        readTimeout: 50000
        loggerLevel: basic

单独设置的优先级大于 default

如何开启日志增强?

openFeign虽然提供了日志增强功能,但是默认是不显示任何日志的,不过开发者在调试阶段可以自己配置日志的级别。
openFeign的日志级别如下:

  • **NONE**:默认的,不显示任何日志;
  • **BASIC**:仅记录请求方法、URL、响应状态码及执行时间;
  • **HEADERS**:除了BASIC中定义的信息之外,还有请求和响应的头信息;
  • **FULL**:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

配置起来也很简单,步骤如下:

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenfeignConfig {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

}

注意:这里的loggerfeign包里的。同时记住把 application.yml中的配置loggerLevel: basic注释掉

接着在 application.yml文件中添加:

logging:
  level:
    com.zhazha.springcloud.service: debug

FULL显示出来的日志:

2022-08-08 20:20:28.650 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] ---> GET http://cloud-payment-service/payment/get/1 HTTP/1.1
2022-08-08 20:20:28.651 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] ---> END HTTP (0-byte body)
2022-08-08 20:20:28.664 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] <--- HTTP/1.1 200 (12ms)
2022-08-08 20:20:28.664 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] connection: keep-alive
2022-08-08 20:20:28.664 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] content-type: application/json
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] date: Mon, 08 Aug 2022 12:20:28 GMT
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] keep-alive: timeout=60
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] transfer-encoding: chunked
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] 
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] {"code":200,"message":"查询成功 port: 8001","data":{"id":1,"serial":"zhazha01"}}
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService        : [PaymentFeignService#getPaymentById] <--- END HTTP (84-byte body)

OpenFeign工作原理

大致原理图:

image.png

分为初始化和拦截两个部分

初始化

在分析代码前,添加上
image.png
basePackages,当然不加也行

@EnableFeignClients注解入手分析

看到@Import注解,第一时间应当想到ImportBeanDefinitionRegistrar接口

还需要想到ImportSelector

同时就需要了解改接口的作用主要目的就是:利用registerBeanDefinitions函数的AnnotationMetadata importingClassMetadata参数获取@EnableFeignClients(basePackages = "com.zhazha.springcloud.service")注解的属性,然后使用BeanDefinitionRegistry registry去注册 Bean Definition

在本例子中AnnotationMetadata importingClassMetadata参数解析的是注解EnableFeignClients,在其他组件中,比如服务的发现与注册中寻找的是@EnableDiscoveryClient注解

AnnotationMetadata importingClassMetadata可以获得com.zhazha.springcloud.OrderFeignMain80启动类上的注解

public interface ImportBeanDefinitionRegistrar {
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {

		registerBeanDefinitions(importingClassMetadata, registry);
	}

	/**
	 * Register bean definitions as necessary based on the given annotation metadata of
	 * the importing {@code @Configuration} class.
	 * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
	 * registered here, due to lifecycle constraints related to {@code @Configuration}
	 * class processing.
	 * <p>The default implementation is empty.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 */
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}

}


在本案例中,OpenFeign使用@Import导入的是FeignClientsRegistrar
从下面函数入手分析:

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}

第一段代码主要分析到的是defaultConfiguration属性,该属性用来自定义所有Feign客户端的配置,使用@Configuration进行配置。当然也可以为某一个Feign客户端进行配置。具体配置方法见@FeignClientconfiguration属性。

没用到不过也注册了一个Bean,但不分析(懒)

核心代码:

	public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        // 获得启动类上的注解EnableFeignClients
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        // 获取该注解的 clients 属性,看看是否有被@feignClient 注解标记的类
        // 我们项目中并未使用该注解的clients属性,所以获取为 clients.length 长度为 0
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
            // 这段代码主要目的就是扫描 basePackages 注解的包路径,本案例中我主动添加了 com.zhazha.springcloud.service
            // 路径,所以会扫描该路径下的所有 FeignClient 注解标记的类
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            // 源码在下面标记 ① 的地方
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
                // 将扫描出来的类都添加到 Set 集合中
                // 在本案例中只能扫描到 PaymentFeignService 类
                // 只有他添加了 @FeignClient(value = "cloud-payment-service") 注解
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                // @FeignClient 注解的类只能是接口
				Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
                // 拿到 FeignClient 注解的所有属性信息
                // 这里只能拿到value = "cloud-payment-service" 的值,其他都是默认的
				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());
                // 此处 name = cloud-payment-service
                // 此处源码:看 ②
                // 可以明显看到优先级
				String name = getClientName(attributes);
                // 获取 FeignClient 注解configuration属性的值
                // 我们什么都没填,但还是注入了一个 bean
				registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 核心代码 看 ③
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

getBasePackages源码:

	protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
		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;
	}

getClientName源码:

	private String getClientName(Map<String, Object> client) {
		if (client == null) {
			return null;
		}
		String value = (String) client.get("contextId");
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("value");
		}
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("name");
		}
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("serviceId");
		}
		if (StringUtils.hasText(value)) {
			return value;
		}

		throw new IllegalStateException(
				"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
	}

registerFeignClient

// 第一个函数是注册类本体
// 第二个是 PaymentService 上的注解元信息,在registerFeignClient方法体中只做了 getClassName 这一项操作
// 注解 @FeignClient 的属性 key value 我们只配置了 value -> cloud-payment-service
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
        // 获取 PaymentService 的类名 com.zhazha.springcloud.service.PaymentFeignService
		String className = annotationMetadata.getClassName();
        // PaymentService 的类加载器
		Class clazz = ClassUtils.resolveClassName(className, null);
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (ConfigurableBeanFactory) registry : null;
        // contextId = cloud-payment-service
		String contextId = getContextId(beanFactory, attributes);
        // name = cloud-payment-service
		String name = getName(attributes);
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
        // 判断application.yml是否设置了 feign.client.refresh-enabled 配置项,我们这里没配置
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
			factoryBean.setUrl(getUrl(beanFactory, attributes));
			factoryBean.setPath(getPath(beanFactory, attributes));
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
			Object fallback = attributes.get("fallback");
			if (fallback != null) {
				factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
            // 需要注意的 FeignClientFactoryBean 实现了 FactoryBean,所以如果需要定义一个对象时
            // 将会调用 FactoryBean 接口的 getObject 方法, 用于定义一个对象
            // 但这段代码并没有被立即调用
			return factoryBean.getObject();
		});
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		definition.setLazyInit(true);
		validate(attributes);

		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
        // 注意看这一行,我们 feign 修饰的对象 PaymentService 并没有被注入,FactoryBean也没有调用 getObject
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

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

		beanDefinition.setPrimary(primary);

		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}
        // 这里将 PaymentSercice 注入到 Spring 容器中
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
        // 这里还是判断是否开启 feign.client.refresh-enabled 
        // 这里我们没有配置,所以不走
        // 但实际上注入了 key = feign.Request.Options-cloud-payment-service 的 paymentService
        // 使用 refreshScope 创建Request.Options bean 定义
		registerOptionsBeanDefinition(registry, contextId);
	}

至此,注册完成,注意此时仅仅只是注册到 DefaultListableBeanFactory容器的 beanDefinitionMap中,并没有实例化!
image.png

从这行代码判断出来的,并没有实例化它,实例化的话需要去拿到远程的实现对象

而实例化的过程需要关注 Spring 异常熟悉的org.springframework.context.support.AbstractApplicationContext#refresh方法

我们来总结下,整个流程:

  1. 使用@ImportFeignClientsRegistrar对象注入到Spring容器
  2. 使用ClassPathScanningCandidateComponentProvider扫描FeignClient注解的BasePackages属性所对应的所有对象,并将其存入candidateComponents容器中
  3. 遍历candidateComponents对象获取FeignClient注解的属性信息
  4. 创建FeignClientFactoryBean对象,并填充这些属性信息,该对象实现了FactoryBean为以后用于容器在初始化单例对象时调用该接口的getObject函数
  5. 创建holder对象,并以 key = com.zhazha.springcloud.service.PaymentFeignService,value = PaymentFeignServicebeanDefinition对象
  6. 这里虽然往容器中存入对象,但明摆着该Bean无法被使用,毕竟只是一个接口,需要特殊处理

实例化过程

OrderFeignController开始入手接着去解析controller下面的PaymentService依赖

image.png

接着从 BeanFactory里面拿到PaymentService接口

image.png

最终他会回到上面我们分享过的源码片段:

BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
    factoryBean.setUrl(getUrl(beanFactory, attributes));
    factoryBean.setPath(getPath(beanFactory, attributes));
    factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
    Object fallback = attributes.get("fallback");
    if (fallback != null) {
        factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
                                : ClassUtils.resolveClassName(fallback.toString(), null));
    }
    Object fallbackFactory = attributes.get("fallbackFactory");
    if (fallbackFactory != null) {
        factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
                                       : ClassUtils.resolveClassName(fallbackFactory.toString(), null));
    }
    return factoryBean.getObject(); // 会在这里创建一个对象
});

接着就能看到核心代码:

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

	/**
	 * @param <T> the target type of the Feign client
	 * @return a {@link Feign} client created with the specified data and the context
	 * information
	 */
	<T> T getTarget() {
        // FeignContext 在 org.springframework.cloud.openfeign.FeignAutoConfiguration#feignContext 中
        // 被注入到 Spring容器中
        // 对于 FeignContext 的说明:①
		FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(url)) {

			if (LOG.isInfoEnabled()) {
				LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
			}
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
            // 这里将会创建一个代理对象 ②
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			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();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}

		applyBuildCustomizers(context, builder);

		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
	}

FeignContext
image.png

FeignContext继承了NameContextFactory抽象类

它的功能主要是为每一个contextId创建一个独立的ApplicationContext
该对象在FeignAutoConfiguration配置类中将FeignContext注入到Spring容器中

image.png

feign.ReflectiveFeign#newInstance

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

至此实例化结束

OpenFeign负载均衡源码分析

前面的创建和实例化都结束了,现在是使用过程

feign.SynchronousMethodHandler#invoke

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

接着会去这个函数:feign.SynchronousMethodHandler#executeAndDecode

核心代码是:response = client.execute(request, options);

接着进入这里:org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute

	public Response execute(Request request, Request.Options options) throws IOException {
		final URI originalUri = URI.create(request.url());
		String serviceId = originalUri.getHost();
		Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
		String hint = getHint(serviceId);
		DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
				new RequestDataContext(buildRequestData(request), hint));
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
				.getSupportedLifecycleProcessors(
						loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
						RequestDataContext.class, ResponseData.class, ServiceInstance.class);
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
        // 核心代码
		ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
        // ...
    }

进入服务选择函数:

	@Override
	public <T> ServiceInstance choose(String serviceId, Request<T> request) {
        // 获取 loadbalancer策略 ①
		ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
		if (loadBalancer == null) {
			return null;
		}
        // 核心代码 ②
		Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
		if (loadBalancerResponse == null) {
			return null;
		}
		return loadBalancerResponse.getServer();
	}

①:
image.png

image.png

最终发现负载均衡策略是RoundRobinLoadBalancer轮询

org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer#choose

	public Mono<Response<ServiceInstance>> choose(Request request) {
		ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
				.getIfAvailable(NoopServiceInstanceListSupplier::new);
		return supplier.get(request).next()
				.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
	}

熟悉的接口:org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer
image.png

回到org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute

	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		final URI originalUri = URI.create(request.url());
		String serviceId = originalUri.getHost();
		String hint = getHint(serviceId);
		DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
				new RequestDataContext(buildRequestData(request), hint));
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
				.getSupportedLifecycleProcessors(
						loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
						RequestDataContext.class, ResponseData.class, ServiceInstance.class);
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
        // 负载均衡,从多个服务提供者列表中选取一个
		ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
		org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
				instance);
        // 有默认的负载均衡策略,所以该分支不走
		if (instance == null) {
			// ...
		}
        // 计算出目标 ip 地址 ①
		String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
        // 创建请求
		Request newRequest = buildRequest(request, reconstructedUrl);
		LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
        // 最后玩这里走,进行请求处理
		return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
				supportedLifecycleProcessors, loadBalancerProperties.isUseRawStatusCodeInResponseData());
	}

然后就是 ip 从哪里获得?
从负载均衡这行ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);获取了目标服务的IP地址

接着在
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();

reconstructURI源码:

	private static URI doReconstructURI(ServiceInstance serviceInstance, URI original) {
        // host = 192.168.19.1 这是我们提供服务的服务器 ip 之一
		String host = serviceInstance.getHost();
        // scheme = http
		String scheme = Optional.ofNullable(serviceInstance.getScheme())
				.orElse(computeScheme(original, serviceInstance));
        // port = 8002
		int port = computePort(serviceInstance.getPort(), scheme);

		if (Objects.equals(host, original.getHost()) && port == original.getPort()
				&& Objects.equals(scheme, original.getScheme())) {
			return original;
		}
        // false
		boolean encoded = containsEncodedParts(original);
        // 组合完毕:http://192.168.19.1:8002/payment/get/1
		return UriComponentsBuilder.fromUri(original).scheme(scheme).host(host).port(port).build(encoded).toUri();
	}

最终在这里处理请求:

	static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
			Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
			org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
			Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced, boolean useRawStatusCodes)
			throws IOException {
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, lbResponse));
        // ...
    }

至此,大体上的源码分析完毕