Spring Cloud Openfeign源码解析,深入浅出

​​​​​​

目录

一、@import

二、启动加载FeignClientsRegistrar

1、启动类上添加的@EnableFeignClients开启feign支持

2、会通过@Import(FeignClientsRegistrar.class)动态注入Bean接口

3、动态装载

4、重点:registerBeanDefinitions主要包含两个函数内容

5、registerFeignClient作用

6、registerBeanDefinitions小结

三、动态代理

1、HystrixTargeter

2、FeignInvocationHandler

四、openfeign的远程调用

五、总结


一、@import

首先谈谈@import(value="class")普通用法引入普通class类,将class注入到IOC容器中。下图还有两种用法,区别于实现方法不同,即实现ImportBeanDefinitionRegistrar接口和实现ImportSelector接口。

Spring Cloud Openfeign源码解析,深入浅出_第1张图片

二、openfeign启动加载过程

1、启动类上添加的@EnableFeignClients开启feign支持

Spring Cloud Openfeign源码解析,深入浅出_第2张图片

2、会通过@Import(FeignClientsRegistrar.class)动态注入Bean接口

Spring Cloud Openfeign源码解析,深入浅出_第3张图片

3、动态装载

FeignClientsRegistrar实现了ImportBeanDefinitionRegistrarSpringBoot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。

Spring Cloud Openfeign源码解析,深入浅出_第4张图片

4、重点:registerBeanDefinitions主要包含两个函数内容

  • registerDefaultConfiguration:扫描@EnableFeignClients进行配置注册
  • registerFeignClients:扫描@EnableFeignClients和 @FeignClient 修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClientBeanDeifinition 添加到 spring 容器中。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   // 将@EnableFeignClients注解中的defaultConfiguration属性注册到beanDefinitionMap缓存
   // 如果启动时不配置defaultConfiguration的话就是空数组    
    registerDefaultConfiguration(metadata, registry);
   // 1) 扫描所有@FeignClient注解的接口,即扫描到所有Feign接口
   // 2) 将每个@FeignClient注解的configuration属性注册到一个缓存map
   // 3) 根据@FeignClient注解元数据生成 FeignClientFactoryBean 的BeanDefinition,
   //    并将这个BeanDefinition注册到一个缓存map
   registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //获取EnableFeignClients注解属性
		Map 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();
			}
            //将defaultConfiguration属性注册到beanDefinitionMap缓存
            //具体由DefaultListableBeanFactory类实现
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}
public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {

		LinkedHashSet candidateComponents = new LinkedHashSet<>();
		//扫描EnableFeignClients的属性
        Map attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		//AnnotationTypeFilter(FeignClient.class)它是用来判断一个类是不是被Spring扫描到的
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
        //获取@EnableFeignClients注解中的clients实例
		final Class[] clients = attrs == null ? null
				: (Class[]) attrs.get("clients");
        //判断是否有实例
		if (clients == null || clients.length == 0) {
            // ClassPathScanningCandidateComponentProvider是Spring提供的工具,可以按自定义的类型,查找classpath下符合要求的class文件。
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set basePackages = getBasePackages(metadata);
            //主要就是将@FeignClient修饰的class封装到set中
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}
        //遍历set集合,实质上set集合内容就是@FeignClient修饰的客户端信息
		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 attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());

				String name = getClientName(attributes);
                //将configuration属性注册到beanDefinitionMap缓存
                //具体由DefaultListableBeanFactory类实现
				registerClientConfiguration(registry, name,
						attributes.get("configuration"));
                //将FeignClient客户端信息通过生成BeanDefinition注册到IOC容器
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

5、registerFeignClient作用

registerFeignClient方法就是去组装BeanDefinition,定义Bean,然后注册到Spring IOC容器

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map attributes) {
   // 获取@FeignClient修饰的接口的class路径
   String className = annotationMetadata.getClassName();
   // 生成一个beanFactory,其会为FeignClientFactoryBean生成一些必要的组件
   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 = contextId + "FeignClient";
   // 获取到FeignClientFactoryBean的beanDefinition
   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;
   }

   // 将beanDefinition封装到holder,后续通过holder可以获取到这个beanDefinition
   BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
         new String[] { alias });
   // 把BeanDefinition的这个bean定义注册到IOC容器
   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

6、registerBeanDefinitions小结

  • 开启扫描FeignClient的入口:
    启动类上添加的@EnableFeignClients注解会通过@Import注解在SpringBoot启动流程中将 ImportBeanDefinitionRegistrar接口的实现类FeignClientsRegistrar注入到启动类的ConfigurationClass的属性中,在注册启动类的BeanDefinition时,会遍历调用其@Import的所有ImportBeanDefinitionRegistrar接口的 registerBeanDefinitions()方法。

  • 扫描FeignClient
    拿到@EnableFeignClients注解中配置的扫描包路径相关的属性,得到要扫描的包路径; 获取到扫描器ClassPathScanningCandidateComponentProvider,然后给其添加一个注解过滤器(AnnotationTypeFilter),只过滤出包含@FeignClient注解的BeanDefinition; 扫描器的findCandidateComponents(basePackage)方法从包路径下扫描出所有标注了@FeignClient注解并符合条件装配的接口;然后将其在BeanDefinitionRegistry中注册一下

三、动态代理

1、HystrixTargeter

       在扫描完前面的注解后,还会做一件事就是加载断路器,在我使用的版本目前还是内置的HystrixTargeter(高版本移除了Hystrix,采用Spring Cloud Circuit Breaker 做限流熔断)

FactoryBean用来创建代理BeanFeignClientFactoryBean实现了FactoryBean.getObject()接口,执行时通过调用getObject()获取被代理Bean的实例。看看getObject()具体做了什么事情

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

	/**
	 * @param  the target type of the Feign client
	 * @return a {@link Feign} client created with the specified data and the context
	 * information
	 */
	 T getTarget() {
		FeignContext context = applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
        //如果url为空,则走负载均衡,生成有负载均衡功能的代理类
		if (!StringUtils.hasText(url)) {
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
            // 判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(type, name, url));
		}
		 // 这是url不为空的情况,即采用直连方式访问提供者,则生成默认的代理类
   if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
      this.url = "http://" + this.url;
   }
   String url = this.url + cleanPath();
   // 从Spring子容器中获取Client
   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);
   }
   // todo 生成默认的代理类
   Targeter targeter = get(context, Targeter.class);
   return (T) targeter.target(this, builder, context,
         new HardCodedTarget<>(this.type, this.name, url));
	}

最终调用的是Target.target()方法,由于该版本openfeign内置的是hystrix实现Target接口。所以最终加载的的就是hystrix那套Spring Cloud Openfeign源码解析,深入浅出_第5张图片 

2、FeignInvocationHandler

被声明为@FeignClient注解的类,在被注入时,最终会生成一个动态代理对象FeignInvocationHandler,在远程调用getStock()时就会进入到invoke()

Spring Cloud Openfeign源码解析,深入浅出_第6张图片

主要采用JDK动态代理的方式,FeignInvocationHandler实现InvocationHandler接口的invoke(),在该方法中通过调用dispatch.get(method).invoke(args)设置SynchronousMethodHandler处理器,用来实现同步远程调用。

//FeignInvocationHandler
  
  private final Target target;
  //处理器缓存
  private final Map dispatch;

//jdk动态代理
@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();
      }
      //获取对应的远程调用处理器
      return dispatch.get(method).invoke(args);
    }

上面的dispatch在启动时,通过加载@FeignClient修饰的类信息,调用create()方法进行加载处理器Spring Cloud Openfeign源码解析,深入浅出_第7张图片

四、openfeign的远程调用

 上面说了在客户端发起远程调用时,具体执行的就是dispatch.get(method).invoke(args)
而它返回的是某个接口处理器SynchronousMethodHandler.invoke()的执行结果。

invoke()中首先就会创建一个http协议的RequestTemplate 请求模板对象

//SynchronousMethodHandler.class

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

后续执行executeAndDecode(),从名字就能看出来该方法就是执行http请求和解码获取Response 返回结果

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    //通过模板对象转化为http的Reuqest报文
    Request request = targetRequest(template);

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

    Response response;
    long start = System.nanoTime();
    try {
      //建立connection,发起http请求
      response = client.execute(request, options);
      //将返回报文build到response中,后续就是进行返回
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);


    if (decoder != null)
      return decoder.decode(response, metadata.returnType());

    CompletableFuture resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");

      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  } 
  

之前看网上都说openfeign用的是Ribbon那么具体在哪使用了呢?接下来看看 client.execute(request, options)执行了哪些内容。

发起请求是通过LoadBalancerFeignClient实现了Client.execute 接口

Spring Cloud Openfeign源码解析,深入浅出_第8张图片

所以进到LoadBalancerFeignClient.execute 中可以看到FeignLoadBalancer.RibbonRequest。谜底就在这!!!以及拿到了properties配置,记得openfeignRibbon会有一个冲突的地方就是都设置了过期时间的情况下,只会采取一个配置 feign 优先。(不过后续OpenFeign2020.0.X往后貌似不再使用Ribbon做负载均衡了)

Spring Cloud Openfeign源码解析,深入浅出_第9张图片

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

到了这里以及拿到了项目配置信息和负载均衡策略,那么执行 executeWithLoadBalancer()可以想到了是要去干什么事情

  • 创建一个可观察对象,一旦订阅,就与服务器异步执行网络调用由负载平衡器选择。如果有任何错误被RetryHandler指定为可检索的,它们将被函数内部使用,而不会被订阅到返回的Observable的Observer观察到。如果重试次数超过了允许的最大次数,则返回的Observable会发出一个最终错误。否则,将发出执行期间的第一个成功结果和重试。(这是官方给出的注释说明,有负载均衡机制,有重试机制)

可以看到在执行execute()方法时,是调用了IClient接口通过FeignLoadBalancer类实现

Spring Cloud Openfeign源码解析,深入浅出_第10张图片

到了这里就是执行的最底层的Client接口了。

Spring Cloud Openfeign源码解析,深入浅出_第11张图片

request.client().execute()调用的是Default默认的实现类。到了这就是真正的结合之前的转化好的报文发起http请求。返回Response对象

Spring Cloud Openfeign源码解析,深入浅出_第12张图片

 5、总结

openfeign简单概括做了几件事

1、启动通过扫描@EnableFeignClients@FeignClient

2、拿到扫描要注册的Bean信息后,注入到一个名为FeignClientFacotoryBeanspring容器中

3、spring容器初始化通过JDK动态代理,获取FeignClientFacotoryBean产生的代理对象Proxy

4、在进行远程调用时,请求会进过InvocationHandler统一处理,封装http上下文,拿到了properties配置,配置负载均衡策略,重试策略,发起http请求

你可能感兴趣的:(java,后端,spring,cloud)