小许的技术驿站——SpringCloud-OpenFeign源码解析笔记

小许的技术驿站——SpringCloud-OpenFeign源码解析笔记

小弟有一个开源项目,希望大家可以多多一键三连,谢谢大家

nirvana-reborn

后续的源码解析也都会进行同步更新上去

1、什么是OpenFeign?

根据上篇博客能够看到在使用 EurekaloadBalance来进行服务调用时,都需要创建 RestTemplate组件进行 Http请求访问,但是如果每次都是用组件进行请求的话,我们在代码上面会写的很麻烦,而且也会很繁琐,所以 SpringCloud整合了 Feign客户端。而 Feign是一个声明性web服务客户端。Spring Cloud集成了EurekaSpring Cloud CircuitBreakerSpring Cloud LoadBalancer,在使用Feign时提供一个负载均衡的http客户端。并且可以解析Spring MVC注解,意思就是说,我们使用 Feign那么我们就可以不需要在手动使用 RestTemplate组件进行请求 Http接口了。

2、怎么使用OpenFeign?

我们首先改造一下我们消费者的代码。

/**
 * 开启Eureka-CLient客户端,OpenFeign客户端
 */
@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class EurekaServiceBApplication {

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

    @RestController
    @Configuration
    public class ServiceBController {
    
      	/**
      	*	SayHello 请求OpenFeign组件
      	**/
        @Autowired
        SayHelloClient sayHelloClient;

        @RequestMapping(value = "/greeting/{name}", method = RequestMethod.GET)
        public String greeting(@PathVariable("name") String name) {
            return sayHelloClient.test(name);
        }
    }
}

/**
 * 声明一个SayHello OpenFeign请求客户端
 */
@FeignClient("ServiceA")
public interface SayHelloClient {
    
  	/**
  	*	基于Http请求 ServiceA 的 /test/sayHello/{test} 接口
  	**/
    @GetMapping("/test/sayHello/{test}")
    String test(@PathVariable("test") String test);
    
}

根据上面的代码我们在请求 ServiceA/test/sayHello/接口时就不需要使用 RestTemplate组件了,而且在我们开发的过程中,就可以直接把Controller层加上一个接口打包成一个Jar包进行调用即可,这样能够保证接口的请求地址与参数都不会出问题,也能够简化开发。

3、@EnableFeignClients 注解详解

小知识:在看Spring相关的代码时,应该多去关注一些注解中 @Import导入的类,在 SpringBoot中应该多多关注 xxxAutoConfiguration类。

我们在 @EnableFeignClients注解中能够看到导入了 FeignClientsRegistrar.class类,从Spring源码结构来看,一般这样的类都是该注解的关键处理类,所以我们应该仔细阅读一下。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 注册全局默认的FeignClient配置组件
		registerDefaultConfiguration(metadata, registry);
    // 注册 @FeignClient 标注的接口
		registerFeignClients(metadata, registry);
	}


}

registerDefaultConfiguration(metadata, registry)主要是根据 @EnableFeignClients注解中的 defaultConfiguration属性加载默认的全局配置类,如果没有配置就默认使用 FeignClientSpecification.class进行注册,而最主要的是如何去注册 被@FeignClient注解标注的接口。

3.1 registerFeignClients(metadata, registry);

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {	
  	//被@FeignClient标注可以注册为 SpringBean的集合
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
  	// 获取@EnableFeignClients的全部属性值
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
  	// 获取@EnableFeignClients属性中的clients属性
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
  	//如果没有手动配置clients属性那么就通过包路径扫描进行注册
		if (clients == null || clients.length == 0) {
      //获取根据 classpath路径进行扫描的Provider类
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
      //扫描被@FeignClient注解标注的接口
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
      //获取扫描包集合
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
      // 把@EnableFeignClients的clients属性class数组添加到candidateComponents集合中
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}
		// 循环注册FeignClient SpringBean
		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");
				// 获取接口上面 @FeignClient标注的注解熟悉
				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());
				//获取 @FeignClient  以contextId->value->name->serviceId 进行逐步获取feign名称
				String name = getClientName(attributes);
        //注册当前FeignClient的配置类
				registerClientConfiguration(registry, name, attributes.get("configuration"));
				//注册FeignClient到Spring容器中
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
}

从上面的源码能够看到,其实 Feign的模式是和 loadbalance源码的模式是差不多的,都是一个 Client或者一个 loadbalance都会去维护一个 Spring ApplicationContext ,需要什么组件直接通过 getBean()进行获取,其配置全部都是通过配置类进行注册到 Spring上下文进行初始化与配置。

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
  	// FeignClient 接口类名称
		String className = annotationMetadata.getClassName();
  	// 反射获取FeignClient 接口类
		Class clazz = ClassUtils.resolveClassName(className, null);
  	// 获取SpringBean Factory
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (ConfigurableBeanFactory) registry : null;
		String contextId = getContextId(beanFactory, attributes);
		String name = getName(attributes);
  	//创建 FeignClientBean并且设置一些Bean的一些属性
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);//设置Bean工厂
		factoryBean.setName(name);//设置Bean名称
		factoryBean.setContextId(contextId);//设置 Feign的上下文Id
		factoryBean.setType(clazz);//设置Bean class类型
		factoryBean.setRefreshableClient(isClientRefreshEnabled());// 设置是否自动刷新Client客户端,默认False
  	// 通过Spring BeanDefinitionBuilder 创建Bean
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
      // 设置当前Feign客户端的请求Url地址
			factoryBean.setUrl(getUrl(beanFactory, attributes));
      // 设置当前Feign客户端的请求地址前缀
			factoryBean.setPath(getPath(beanFactory, attributes));
      // 设置当前Feign客户端请求404时,是解析请求还是抛出异常,默认false
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
      //判断当前Feign客户端是否有返回处理对象,如果有就设置,当时返回的处理对象必须是SpringBean
			Object fallback = attributes.get("fallback");
			if (fallback != null) {
				factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
      //判断当前Feign客户端是否有返回处理对象工厂,该工厂必须要生成返回处理对象,也必须是SpringBean,如果有就设置
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
      // 这里是最关键的一步,获取到的对象被 FeignClientFactoryBean动态代理处理过,在调用时会自动去请求Eureka服务接口
			return factoryBean.getObject();
		});
  	// 自动注入方式,根据类型自动注入
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  	// 是否是懒加载
		definition.setLazyInit(true);
		validate(attributes);
		
  	//从BeanDefinitionBuilder中获取上面配置的 BeanDefinition
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
  	// 设置当前的 BeanDefinition 注入类型是 接口类型
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
  	// 设置 feignClientsRegistrarFactoryBean 其根本是 FeignClientFactoryBean 对象,
  	// 主要是 getObject()方法获取被动态代理过的 FeignClient接口
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
		// 当前的beanDefinition是首先的,首先注入的
		boolean primary = (Boolean) attributes.get("primary");
		beanDefinition.setPrimary(primary);
		// 当前Feign是否有别名,默认为空
		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}
		//创建一个带有名称与别名的 Holder,其本身还是beanDefinition
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
  	// 注册 beanDefinition
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
		// 注册一个 refreshScope 默认不创建
		registerOptionsBeanDefinition(registry, contextId);
	}

根据这段源码我们看到了,其实被 @FeignClient标注的接口,会被注册成 SpringBean而在通过 FeignClientFactoryBean进行获取 Bean的时候,获取到的 Bean对象其实是被 Feign动态代理生成过了,这时候在调用接口的时候,就会被 Feign通过动态代理的方式去请求。

3.2 FeignClientFactoryBean#getObject

@Override
public Object getObject() {
		return getTarget();
}
<T> T getTarget() {
		FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
  	//创建一个 Feign.Builder 并且加载对应的 Feign配置文件参数
		Feign.Builder builder = feign(context);
  	//判断在 @FeignClient注解中是否配置了 url 如果没有配置那么就默认使用 @FeignClient name 名称作为请求地址
		if (!StringUtils.hasText(url)) {
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
      // 生成一个 loadbalance的请求动态代理对象
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
  	// 如果设置了 url属性,那么就根据 url设置请求地址
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
  	// 获取Eureka Client 客户端,并且判断Eureka Client是使用那种方式进行请求,默认是 FeignBlockingLoadBalancerClient.class
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof FeignBlockingLoadBalancerClient) {
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}
  	// 获取动态代理增强对象,默认是 DefaultTargeter对象
		Targeter targeter = get(context, Targeter.class);
  	// 使用 DefaultTargeter 生成一个动态代理对象
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

读取配置文件配置FeignClient客户端

protected Feign.Builder feign(FeignContext context) {
  	// 从 Spring 容器中获取 logger工厂,默认是 Slf4jLogger
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(type);
  	// 从 Spring 容器中获取 Feign.Builder ,并且配置参数初始值
		Feign.Builder builder = get(context, Feign.Builder.class)
				.logger(logger)//配置日志对象
				.encoder(get(context, Encoder.class))//配置请求参数编码器
				.decoder(get(context, Decoder.class))//配置请求参数解码器
				.contract(get(context, Contract.class));//配置SpringMVC 注解解析器
		// 根据配置文件配置FeignClient配置参数
		configureFeign(context, builder);
  	// 执行用户自己定义的创建 Feign Client构建器
		applyBuildCustomizers(context, builder);

		return builder;
	}

	private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) {
    // 由此能够知道自己定义的Feign构建,必须也是SpringBean
		Map<String, FeignBuilderCustomizer> customizerMap = context.getInstances(contextId,
				FeignBuilderCustomizer.class);

		if (customizerMap != null) {
			customizerMap.values().stream().sorted(AnnotationAwareOrderComparator.INSTANCE)
					.forEach(feignBuilderCustomizer -> feignBuilderCustomizer.customize(builder));
		}
		additionalCustomizers.forEach(customizer -> customizer.customize(builder));
	}

	protected void configureFeign(FeignContext context, Feign.Builder builder) {
    //从Spring容器中获取到 Feign Client 配置文件,主要是在 application.properties文件中配置的参数
		FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
				: applicationContext.getBean(FeignClientProperties.class);
		//从Spring容器中获取到当前Feign Client的全局配置
		FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
		setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
		// 配置文件的读取流程是 全局的配置->Default默认配置参数->clientProperties单个feign client配置参数
    // 覆盖顺序是对底层的覆盖最上层的
		if (properties != null && inheritParentContext) {
			if (properties.isDefaultToProperties()) {
        //配置FeignClient全局配置参数
				configureUsingConfiguration(context, builder);
        //配置FeignClient默认配置参数
				configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
        //针对FeignClient单个服务读取配置参数
				configureUsingProperties(properties.getConfig().get(contextId), builder);
			}
			else {
				configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
				configureUsingProperties(properties.getConfig().get(contextId), builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
			configureUsingConfiguration(context, builder);
		}
	}

	protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
    // 整体配置全部都是从Spring容器中获取对应的Bean对象,然后放入 Feign.Builder 对象中
		Logger.Level level = getInheritedAwareOptional(context, Logger.Level.class);
		if (level != null) {
			builder.logLevel(level);
		}
		Retryer retryer = getInheritedAwareOptional(context, Retryer.class);
		if (retryer != null) {
			builder.retryer(retryer);
		}
		。。。
	
	}

	protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config,
			Feign.Builder builder) {
    // 通过用户在application.properties配置文件中的配置,进行逐步设置相关参数
		if (config == null) {
			return;
		}

		if (config.getLoggerLevel() != null) {
			builder.logLevel(config.getLoggerLevel());
		}
		//是否自动刷新feign client
		if (!refreshableClient) {
			connectTimeoutMillis = config.getConnectTimeout() != null ? config.getConnectTimeout()
					: connectTimeoutMillis;
			readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout() : readTimeoutMillis;
			followRedirects = config.isFollowRedirects() != null ? config.isFollowRedirects() : followRedirects;

			builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS, readTimeoutMillis,
					TimeUnit.MILLISECONDS, followRedirects));
		}

		if (config.getRetryer() != null) {
			Retryer retryer = getOrInstantiate(config.getRetryer());
			builder.retryer(retryer);
		}
		。。。
	}

获取EurekaClient,并且生成动态代理对象

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
  	// 从Spring容器中获取到EurekaClient,默认是 FeignBlockingLoadBalancerClient 对象
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
     	// 从Spring容器中获取 Targeter 动态代理类,默认是 DefaultTargeter
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}
}
class DefaultTargeter implements Targeter {
  // 通过该代码能够看到,动态代理生成的对象还是通过Feign.Builder进行生成的
	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
			Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}
}
public Feign build() {
  	// 对当前配置的FeignClient的参数根据this.capabilities集合进行增强,默认 this.capabilities 为空
    Client client = (Client)Capability.enrich(this.client, this.capabilities);
    Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
    List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
      return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
    }).collect(Collectors.toList());
    Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
    Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
    Options options = (Options)Capability.enrich(this.options, this.capabilities);
    Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
    Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
    InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
    QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
    Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
    ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
  	// 返回一个可以生成FeignClient动态代理的对象
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

public <T> T newInstance(Target<T> target) {
  	//根据 target对象创建对应方法的SpringMVC注解的MethodHandler,创建的MethodHandler是SynchronousMethodHandler
  	//所以在方法动态代理调用是,主要还是调用SynchronousMethodHandler 的invoke方法
    Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
  	// 用于存储方法与解析SpringMVC注解的MethodHandler
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
  	// 当 target 是 Object对象是,则会用到该list
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
  	// 获取当前接口所有方法集合
    Method[] var5 = target.type().getMethods();
    int var6 = var5.length;
		// 循环所有方法集合
    for(int var7 = 0; var7 < var6; ++var7) {
      Method method = var5[var7];
      // 判断当前的方法是存在于对象中还是接口中
      if (method.getDeclaringClass() != Object.class) {
        // 当前的方法在对象(Object)中
        if (Util.isDefault(method)) {
          DefaultMethodHandler handler = new DefaultMethodHandler(method);
          defaultMethodHandlers.add(handler);
          methodToHandler.put(method, handler);
        } else {
          // 当前方法在接口(interface)中
          //并且对该方法解析SpringMVC注解的nameToHandler放一起
          methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
      }
    }
		// 通过动态代理构造工厂 InvocationHandlerFactory 创建一个 FeignInvocationHandler动态代理 Handler
    InvocationHandler handler = this.factory.create(target, methodToHandler);
  	// 生成一个动态代理对象
    T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
  	// 若是对象中的方法则通过默认的 DefaultMethodHandler 绑定到创建成功的proxy动态代理上面
    Iterator var12 = defaultMethodHandlers.iterator();
    while(var12.hasNext()) {
      DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
      defaultMethodHandler.bindTo(proxy);
    }
		//返回一个该 类/接口 的动态代理
    return proxy;
}

从上面的代码中可以看到,当 调用FeignClientFactoryBean#getObject方法时,其实获取到的是一个已经被 Feign使用 Java原生的动态代理进行代理过的对象即 FeignInvocationHandler对象,这时候就知道我们在Spring自动注入 Feign Client的时候其实注入的就是 FeignInvocationHandler对象,接下来,我们来看一下,当我们调用 Feign Client请求服务接口时的调用流程。

4、OpenFeign 动态代理调用Eureka服务接口

FeignInvocationHandler动态代理核心方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (!"equals".equals(method.getName())) {
        if ("hashCode".equals(method.getName())) {
          return this.hashCode();
        } else {
          																																// 主要是这里通过创建时放入的methodToHandler,根据动态代理传过																																					// 来的方法获取该方法的MethodHandler,进行调用Eureka服务
          return "toString".equals(method.getName()) ? this.toString() : ((MethodHandler)this.dispatch.get(method)).invoke(args);
        }
     } else {
        try {
          Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return this.equals(otherHandler);
        } catch (IllegalArgumentException var5) {
          return false;
        }
    }
}

SynchronousMethodHandler方法动态代理执行源码

public Object invoke(Object[] argv) throws Throwable {
  	// 根据方法的SpringMVC注解进行解析请求,创建RequestTemplate
    RequestTemplate template = this.buildTemplateFromArgs.create(argv);
  	// 获取请求超时等参数
    Options options = this.findOptions(argv);
  	// 获取重试机制
    Retryer retryer = this.retryer.clone();

    while(true) {
      try {
        // 执行http请求调用
        return this.executeAndDecode(template, options);
      } catch (RetryableException var9) {
        // feign 重试机制
        RetryableException e = var9;
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException var8) {
          Throwable cause = var8.getCause();
          if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
            throw cause;
          }
          throw var8;
        }

        if (this.logLevel != Level.NONE) {
          this.logger.logRetry(this.metadata.configKey(), this.logLevel);
        }
      }
    }
}
	//请求数据与解码响应数据
 Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
   //执行请求的一些Interceptor拦截器
   Request request = this.targetRequest(template);
 	 //开始请求时间
   long start = System.nanoTime();
   Response response;
   try {
     //通过 FeignBlockingLoadBalancerClient进行执行eureka接口调用
     response = this.client.execute(request, options);
     //根据response响应数据builder一下整理返回数据
     response = response.toBuilder().request(request).requestTemplate(template).build();
   } catch (IOException var12) {
     // 如果Http请求出现IOException就是通信异常,就会throw FeignException 异常
     // 在调用前已经 try catch捕获到FeignException该,并且也有相关的重试机制
     throw FeignException.errorExecuting(request, var12);
   }
		// 获取请求调用消耗时长
   long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
   if (this.decoder != null) {
     // 根据Feign的解码器将返回的数据处理成该方法的返回类型
     return this.decoder.decode(response, this.metadata.returnType());
   } else {
     // 如果没有自定义的解码器,就直接调用当前异步返回方法类型其核心还是使用默认的Decoder
     CompletableFuture<Object> resultFuture = new CompletableFuture();
     this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);
     try {
       if (!resultFuture.isDone()) {
         throw new IllegalStateException("Response handling not done");
       } else {
         return resultFuture.join();
       }
     } catch (CompletionException var13) {
       Throwable cause = var13.getCause();
       if (cause != null) {
         throw cause;
       } else {
         throw var13;
       }
     }
   }
}

根据上面的代码能够看到,其实调用的方式还是使用的 FeignBlockingLoadBalancerClientFeignBlockingLoadBalancerClient也属于 LoadBalancer组件的范畴,所以在进行Eureka服务地址选择时,还是走的 LoadBalancer的那一套东西,只不过在获取到相应的请求地址时,会生成一个 Request请求,最核心还是调用 feign.Client.Default#convertResponse方法进行http调用

5、总结

从上面的源码解析,我们能够看到,OpenFeign的整体流程是

1、先根据 @EnableFeignClients注解所在的包名进行扫描下面所有的 @FeignClient接口,并且配置相关配置信息参数

2、获取到所有标注 @FeignClient接口进行循环创建 BeanDefinitionBuilder Spring Bean

3、每个BeanDefinitionBuilder都是一个 FactoryBean所以就会有返回一个通过 JDK 动态代理生成的对象进行返回给 Spring容器,而在通过Spring注入时,就会将动态代理过后的对象注入进去。即、FeignInvocationHandler

4、在调用 Feign Client接口时通过 FeignInvocationHandler动态代理对象进行远程调用 Eureka远程服务,主要是根据 FeignBlockingLoadBalancerClient通过 LoadBalancer组件进行获取对应的服务请求地址并且组装成一个 Request,通过 feign.Client.Default#convertResponse方法进行Http调用

5、在调用结束后通过返回的数据进行 Decoder解码返回参数,返回给调用方法

自此以上 OpenFeign主要流程已全部解析完毕

上一篇:【常见Web应用安全问题】---4、Directory traversal


下一篇:服务接口调用-OpenFeign