3.1springCloud学习之-Feign源码学习

·  阅读 361

1.背景

今天我们学习SpringCloud的客户端负载均衡Ribbon

我们今天继续使用之前eureka-server作为服务注册中心

使用Springboot和springcloud的版本如下

  • springboot版本:2.3.5-release
  • springcloud版本:Hoxton.SR9

2.feign远程的基本流程

Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。Feign远程调用的基本流程,大致如下图所示

image

从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用

2.1 Feign的重要组件

在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。

  1. @EnableFeignClients
  2. @FeignClient

3.源码解读

@EnableFeignClients

从这个注解是入口,我们可以看到使用@Import这个注解,这是注解我们在使用springboot中经常遇到,主要作用就是将Bean加载在IOC容器中

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients
复制代码

FeignClientsRegistrar

这个方式主要是加载一下默认配置,同时扫描@FeignClient的注解的类加载在IOC容器

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
			//特别重要:加载一些默认配置
		registerDefaultConfiguration(metadata, registry);
		//特别重要:扫描@FeignClient注解加入到IOC
		registerFeignClients(metadata, registry);
	}
复制代码
public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
            //特别重要:扫描@FeignClient的注解
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			//加载在IOC容器
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}
复制代码
private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		//特别重要:将FeignClient对象包装成FeignClientFactoryBean对象。偷天换日(Spring+mybaties也是这样的)
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}
复制代码

FeignClientFactoryBean的分析

实现类Spring的FactoryBean的工程bean。

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware
复制代码

该类主要有是三个方法

这个IOC容器在获取Bean的时候会调用getObject的方法,这里是代理的入口

	@Override
	public Object getObject() throws Exception {
		return getTarget();
	}
复制代码
<T> T getTarget() {
        // 二、实例化Feign上下文对象FeignContext
		FeignContext context = applicationContext.getBean(FeignContext.class);
		// 三、生成builder对象,用来生成feign
		Feign.Builder builder = feign(context);
        //特别重要:判断是否有url,没有则是负载均衡算法
		if (!StringUtils.hasText(url)) {
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			// 四、生成负载均衡代理类
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(type, name, url));
		}
	}
复制代码

生成代理类

	protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		//特别重要:获取feignClient,有三种情况
		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?");
	}
复制代码

FeignClient用三种实现

1)没有整合ribbon、sleuth: 获取默认的Client:Default实例。

2)整合了ribbon,没有整合sleuth: 获取LoadBalanceFeignClient实例。

3)整合了ribbon 和 sleuth: 会获取TraceFeignClient实例,该实例是对LoadBalanceFeignClient的一种包装,实现方式通过BeanPostProcessor实现:FeignBeanPostProcessor中定义了包装逻辑:

HystrixTargeter或者DefaultTargeter

class HystrixTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		// 判断是否加入hystrix
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
	}
复制代码
    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }
复制代码

ReflectiveFeign

public Feign build() {
     
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
  }
复制代码
  public <T> T newInstance(Target<T> target) {
    //特别重要:FeignInvocationHandler,HystrixInvocationHandler
    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;
  }
复制代码

本次我们使用FeignInvocationHandler

FeignInvocationHandler

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
      return dispatch.get(method).invoke(args);
    }
复制代码

feign.SynchronousMethodHandler#invoke

@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) {

    }
  }
复制代码
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);
    //特别重要:执行最终的方法:LoadBalancerFeignClient
      response = client.execute(request, options);

复制代码

LoadBalancerFeignClient

这里使用到了RIbbon的负载均衡策略

@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);
		}
	}
复制代码

4.0 流程总结

由于Feign远程调用接口的JDK Proxy实例的InvokeHandler调用处理器有多种,导致Feign远程调用的执行流程,也稍微有所区别,但是远程调用执行流程的主要步骤,是一致的。这里主要介绍两类JDK Proxy实例的InvokeHandler调用处理器相关的远程调用执行流程:

(1)与 默认的调用处理器 FeignInvocationHandler 相关的远程调用执行流程;

(2)与 Hystrix调用处理器 HystrixInvocationHandler 相关的远程调用执行流程。

介绍过程中,还是以前面的DemoClient的JDK Proxy远程动态代理实例的执行过程为例,演示分析Feigh远程调用的执行流程。

1.1.1 与 FeignInvocationHandler 相关的远程调用执行流程 FeignInvocationHandler是默认的调用处理器,如果不对Feign做特殊的配置,则Feign将使用此调用处理器。结合前面的DemoClient的JDK Proxy远程动态代理实例的hello()远程调用执行过程,在这里,详细的介绍一下与 FeignInvocationHandler 相关的远程调用执行流程,大致如下图所示。 image

​ 图6 与 FeignInvocationHandler 相关的远程调用执行流程

整体的远程调用执行流程,大致分为4步,具体如下:

第1步:通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。

前文讲到,Feign在启动时,会为加上了@FeignClient注解的所有远程接口(包括 DemoClient 接口),创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。在这里,暂且将这个Proxy代理实例,叫做 DemoClientProxy,稍后,会详细介绍这个Proxy代理实例的具体创建过程。

然后,在本实例的UserController 调用代码中,通过@Resource注解,按照类型或者名称进行匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给@Resource注解所在的成员变量,本实例的成员变量的名称为 demoClient。

在需要代进行hello()远程调用时,直接通过 demoClient 成员变量,调用JDK Proxy动态代理实例的hello()方法。

第2步:执行 InvokeHandler 调用处理器的invoke(…)方法

前面讲到,JDK Proxy动态代理实例的真正的方法调用过程,具体是通过 InvokeHandler 调用处理器完成的。故,这里的DemoClientProxy代理实例,会调用到默认的FeignInvocationHandler 调用处理器实例的invoke(…)方法。

通过前面 FeignInvocationHandler 调用处理器的详细介绍,大家已经知道,默认的调用处理器 FeignInvocationHandle,内部保持了一个远程调用方法实例和方法处理器的一个Key-Value键值对Map映射。FeignInvocationHandle 在其invoke(…)方法中,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的 MethodHandler 方法处理器,然后由后者完成实际的HTTP请求和结果的处理。

所以在第2步中,FeignInvocationHandle 会从自己的 dispatch映射中,找到hello()方法所对应的MethodHandler 方法处理器,然后调用其 invoke(…)方法。

第3步:执行 MethodHandler 方法处理器的invoke(…)方法

通过前面关于 MethodHandler 方法处理器的非常详细的组件介绍,大家都知道,feign默认的方法处理器为 SynchronousMethodHandler,其invoke(…)方法主要是通过内部成员feign客户端成员 client,完成远程 URL 请求执行和获取远程结果。

feign.Client 客户端有多种类型,不同的类型,完成URL请求处理的具体方式不同。

第4步:通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果

如果MethodHandler方法处理器实例中的client客户端,是默认的 feign.Client.Default 实现类性,则使用JDK自带的HttpURLConnnection类,完成远程 URL 请求执行和获取远程结果。

如果MethodHandler方法处理器实例中的client客户端,是 ApacheHttpClient 客户端实现类性,则使用 Apache httpclient 开源组件,完成远程 URL 请求执行和获取远程结果。

通过以上四步,应该可以清晰的了解到了 SpringCloud中的 feign 远程调用执行流程和运行机制。

实际上,为了简明扼要的介绍清楚默认的调用流程,上面的流程,实际上省略了一个步骤:第3步,实际可以分为两小步。为啥呢? SynchronousMethodHandler 并不是直接完成远程URL的请求,而是通过负载均衡机制,定位到合适的远程server 服务器,然后再完成真正的远程URL请求。换句话说,SynchronousMethodHandler实例的client成员,其实际不是feign.Client.Default类型,而是 LoadBalancerFeignClient 客户端负载均衡类型。 因此,上面的第3步,如果进一步细分话,大致如下:(1)首先通过 SynchronousMethodHandler 内部的client实例,实质为负责客户端负载均衡 LoadBalancerFeignClient 实例,首先查找到远程的 server 服务端;(2) 然后再由LoadBalancerFeignClient 实例内部包装的feign.Client.Default 内部类实例,去请求server端服务器,完成URL请求处理。

5.自己绘制的一个流程图

参考文章:

www.cnblogs.com/crazymakerc…

分类:
后端
标签: