feign源码解析 - 初始化

基于spring-cloud-openfeign-core-2.2.5.RELEASE

1. 概述

本文以spring-cloud-openfeign-core-2.2.5.RELEASE为基础,介绍spring cloud如何对feign进行扩展,简化接入复杂度,降低接入成本。

2. 入口@EnableFeignClients

SpringCloud项目中对Feign使用,基于一贯的注解驱动方式。 通过简单地配置注解@EnableFeignClients,SpringCloud就会扫描指定package,生成可供直接调用的feign实现类。

2.1 FeignClientsRegistrar类型

@EnableFeignClients注解最终导致FeignClientsRegistrar被引入到Spring容器,参与Spring生命周期。

FeignClientsRegistrar实现了诸多Spring典型接口,其中最关键的是对于ImportBeanDefinitionRegistrar接口的实现。

	// ===================================== FeignClientsRegistrar.registerBeanDefinitions(...)
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    
    
		// 向 Spring容器中注入一个 FeignClientSpecification Bean(该Bean 名词以 default. 开头)。
		// 注意 FeignClientSpecification 实现了 NamedContextFactory.Specification 接口, 形成Spring父子容器. 
		registerDefaultConfiguration(metadata, registry);
		// 根据注解EnableFeignClients指定的package, 扫描其下注解@FeignClient的接口类型, 根据接口和注解信息, 1:1向Spring容器中注册一个FeignClientFactoryBean实例。 
		// 参见下方讲解
		registerFeignClients(metadata, registry);
	}

	// ===================================== FeignClientsRegistrar.registerFeignClients(...)
	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    
    
		......
	
		// 扫描用户指定package
		for (String basePackage : basePackages) {
    
    
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
    
    
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
    
    
					// verify annotated class is an interface
					// 被@FeignClient注解的必须是接口类型
					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);
					// 为每个@FeignClient注解的接口类型, 向Spring父容器中注册一个对应的子容器FeignClientSpecification.
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					// 针对每个`@FeignClient`注解的接口, 生成并向容器中注册一个对应实现类的实例Bean, 这项工作由FeignClientFactoryBean完成
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

2.2 FeignClientFactoryBean类型

该类负责为被@FeignClient注解的接口, 生成一个对应的实现类,供使用者调用。

FeignClientFactoryBean继承自Spring中的典型接口FactoryBean<T>,所以核心方法为getObject()

	// FeignClientFactoryBean.getObject()
	@Override
	public Object getObject() throws Exception {
    
    
		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实例, 在 FeignAutoConfiguration 中实现注入。
		FeignContext context = applicationContext.getBean(FeignContext.class);
		// 根据用户配置, 从Spring容器中取出一系列feign组件, 组装构造出一个 Feign.Builder 实例。
		// 注意这里就应用到了上面提到过的父子容器关系, Spring将优先从服务对应的子容器中取配置项.
		Feign.Builder builder = feign(context);
		
		// ...... 减少复杂度, 这里我们先不考虑LoadBalancer
		
		// 从容器中取出Targeter实现类, 来生成@FeignClient注解接口的实现类.
		// 这里依然是为了减少干扰项, 我们禁用默认的hystrix, 所以这里的Targeter实现类为`DefaultTargeter`
		// 另外需要注意的一点是Targeter和Target的区别:
		//	1. Targeter定义在spring-cloud-openfeign-core中。 主要用途是作为生成@FeignClient接口实现类的统一调用入口, 正如下面这两行代码。
		//	2. Target定义在feign-core中。 代表@FeignClient接口在feign内部的实体表现。 (其中的type就是@FeignClient接口的类型); Targeter.target(...) 最后一个方法参数正是Target实现类HardCodedTarget。
		Targeter targeter = get(context, Targeter.class);
		// 构建出@FeignClient实现类实例的逻辑, 在这里由spring-cloud-openfeign-core中的Targeter转交给feign-core中的Feign.Builder。	
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(type, name, url));
	}

3. Feign.newInstance(Target<T> target)(生成Proxy实例)

本方法主要负责基于JDK的Proxy功能,生成@FeignClient所注解接口的实现类。

  // `Targeter.target(...)` 只是简单地将处理逻辑调度给了`Feign.newInstance(Target<T> target)` 
  // 在本文场景下,Feign实现类为ReflectiveFeign 。
  // ===================================== ReflectiveFeign.newInstance(...)	
  @Override
  public <T> T newInstance(Target<T> target) {
    
    
  	// 使用内部类型ParseHandlersByName, 解析当前@FeignClient接口类型, 将所定义的接口方法解析为MethodHandler实例(借助`Contract`实现类), 并最终以Map数据结构返回.
    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)) {
    
     // 如果为default方法
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
    
    
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
	
	// 借助自定义接口InvocationHandlerFactory, 使用工厂模式提供对外统一的InvocationHandler实例生成。正是借助InvocationHandlerFactory接口, 实现了对于 Hystrix 的支持. 
	// 当前场景下, InvocationHandler实现类为ReflectiveFeign.FeignInvocationHandler, 另外一个则是Hystrix相关的HystrixInvocationHandler。 再补充一个的就是Sentinel相关的SentinelInvocationHandler。 
	// 基于@FeignClient接口方法发起的http调用执行, 最终都会跳转到`FeignInvocationHandler`对于接口InvocationHandler接口方法`invoke(Object proxy, Method method, Object[] args)`的实现中。
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 熟悉的JDK Proxy特性。
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {
    
    target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    
    
      defaultMethodHandler.bindTo(proxy);
    }
    // 这里返回的实例, 正是@FeignClient接口的实现类实例。 使用者通过@Autowired引入的@FeignClient实例正是它。 
    return proxy;
  }

4. 整体时序图

以上处理流程,串起来就是下面这副feign_init时序图了。
feign_init

5. 各组件意义

组件类型 说明
Targeter spring-cloud-openfeign-core-2.2.5.RELEASE.jar 中定义。提供了@FeignClient接口实现类生成的对外统一门面。
Target feign-core-10.10.1.jar 中定义。代表被@FeignClient注解接口在Feign体系内的内部实例。
Contract 负责解析被@FeignClient注解接口类型,将方法上标注的注解解析成 MethodMetadata
MethodMetadata 被@FeignClient注解接口内定义的方法,解析为MethodMetadata
MethodHandler 实际http请求发起的入口。类似于InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}
RequestTemplate RequestTemplate.Factory接口实现基于方法参数构建出RequestTemplate实例。
Target则负责依据RequestTemplate实例构建出相应的Request实例,用作马上要开始的http调用。
Feign 负责生成@FeignClient注解接口的实现类。关键方法T newInstance(Target<T> target)
Client 抽象了发起http调用,获取响应这套流程。
Request / Response 作为Client接口的入参和出参,很明显这两个类型代表了feign内部对于http请求和返回值的抽象。另外这两个类型均被final修饰,说明feign设计里没打算让外界在这个维度进行扩展。
Logger feign内部抽象出来的记录日志的接口。将日志记录功能拆分为logRequest(...)logAndRebufferResponse(...)logRetry(...)logIOException(...) 阶段。子类需要实现唯一的抽象方法log(...)
Encoder / Decoder 分别负责入参的编码,以及出参的解码。默认情况未生效。
Retryer 抽象了发生异常时候,是否重试的逻辑实现。
RequestInterceptor 典型的拦截器模式,用于http请求发起前的自定义扩展需求。生效位置:SynchronousMethodHandler.targetRequest(RequestTemplate template)
InvocationHandlerFactory InvocationHandler工厂模式。Hystri和Sentinel正是基于本扩展接口实现与feign的集成。

6. 补充

6.1 @FeignClient注解接口中所定义方法的一些高级用法

方法参数:

  1. URI类型。用作动态host。
    a. Contract.BaseContract.parseAndValidateMetadata(Class<?> targetType, Method method)负责解析。
    b. BuildTemplateByResolvingArgs.create(Object[] argv) 负责确保调用接口方法传参时候,参数不为null,且类型满足URI要求。
  2. Options类型。
    a. 生效位置: SynchronousMethodHandler.findOptions(Object[] argv)

7. 总结

@FeignClient注解的接口类型:

  1. 内部定义的每个方法,对应一个http调用。
  2. 方法本身的定义,以及方法上的注解每个配置项,对应解析为http调用时的一个参数项。该职责由 Contract契约接口实现类来完成。
  3. 基于JDK 的 Proxy.newProxyInstance 构建出该接口实现类( 典型源码位置为: T ReflectiveFeign.newInstance(Target<T> target) )。 相应的 InvocationHandler实现类为: ReflectiveFeign.FeignInvocationHandlerHystrixInvocationHandler 。(抽象出专门的InvocationHandlerFactory工厂接口来提供相应的对外扩展)。
  4. 使用者拿着该接口实现类,调用接口定义的方法,最终实现对于相应Http远程服务调用。
  5. 通过强类型化的面向接口编程习惯,确保了接口调用方式的一致性,减缓系统腐朽速度。

8. 参考

  1. 关于FeignClient的使用大全——进阶篇
  2. 深入理解 OpenFeign 的架构原理
  3. Spring Cloud Feign设计原理

猜你喜欢

转载自blog.csdn.net/lqzkcx3/article/details/127186374