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. 各组件意义

组件类型说明
Targeterspring-cloud-openfeign-core-2.2.5.RELEASE.jar 中定义。提供了@FeignClient接口实现类生成的对外统一门面。
Targetfeign-core-10.10.1.jar 中定义。代表被@FeignClient注解接口在Feign体系内的内部实例。
Contract负责解析被@FeignClient注解接口类型,将方法上标注的注解解析成 MethodMetadata
MethodMetadata被@FeignClient注解接口内定义的方法,解析为MethodMetadata
MethodHandler实际http请求发起的入口。类似于InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}
RequestTemplateRequestTemplate.Factory接口实现基于方法参数构建出RequestTemplate实例。
Target则负责依据RequestTemplate实例构建出相应的Request实例,用作马上要开始的http调用。
Feign负责生成@FeignClient注解接口的实现类。关键方法T newInstance(Target<T> target)
Client抽象了发起http调用,获取响应这套流程。
Request / Response 作为Client接口的入参和出参,很明显这两个类型代表了feign内部对于http请求和返回值的抽象。另外这两个类型均被final修饰,说明feign设计里没打算让外界在这个维度进行扩展。
Loggerfeign内部抽象出来的记录日志的接口。将日志记录功能拆分为logRequest(...)logAndRebufferResponse(...)logRetry(...)logIOException(...) 阶段。子类需要实现唯一的抽象方法log(...)
Encoder / Decoder分别负责入参的编码,以及出参的解码。默认情况未生效。
Retryer抽象了发生异常时候,是否重试的逻辑实现。
RequestInterceptor典型的拦截器模式,用于http请求发起前的自定义扩展需求。生效位置:SynchronousMethodHandler.targetRequest(RequestTemplate template)
InvocationHandlerFactoryInvocationHandler工厂模式。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设计原理
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`feign-spring-mvc-starter` 是一个 Feign 的扩展,它支持使用 Spring MVC 注解来定义和调用 REST 服务。使用 `feign-spring-mvc-starter`,你可以像使用 Spring MVC 控制器一样定义 Feign 客户端,从而更方便地进行 REST 服务的开发。 在使用 `feign-spring-mvc-starter` 之前,你需要先了解 FeignSpring MVC 的基本概念和用法。 Feign 是一个声明式的 Web 服务客户端,它可以帮助你更方便地定义和调用 REST 服务。Feign 的基本使用方法是定义一个接口,用于描述 REST 服务的 API,然后使用 Feign 注解来声明这个接口。 Spring MVC 是一个基于 Java 的 Web 框架,它提供了一组注解和 API,用于处理 Web 请求和响应。 `feign-spring-mvc-starter` 将 FeignSpring MVC 结合起来,使你可以使用 Spring MVC 注解来定义和调用 REST 服务。使用 `feign-spring-mvc-starter`,你可以更方便地使用 Feign 来调用 REST 服务。 以下是一个使用 `feign-spring-mvc-starter` 的示例: 1. 添加 Maven 依赖 在 pom.xml 文件中添加以下依赖项: ```xml <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-spring-mvc</artifactId> <version>5.3.1</version> </dependency> ``` 2. 定义 Feign 接口 定义一个 Feign 接口,用于描述 REST 服务的 API。例如: ```java @FeignClient(name = "example-service") public interface ExampleClient { @GetMapping("/example") String getExample(); } ``` 在这个接口中,我们使用了 `@FeignClient` 注解来声明这个接口是一个 Feign 客户端,并指定了服务的名称。然后,我们定义了一个 `getExample()` 方法,用于调用 example-service 服务的 /example 路径。 3. 定义 Spring MVC 控制器 定义一个 Spring MVC 控制器,用于处理来自客户端的请求。例如: ```java @RestController public class ExampleController { private final ExampleClient exampleClient; public ExampleController(ExampleClient exampleClient) { this.exampleClient = exampleClient; } @GetMapping("/") public String index() { return exampleClient.getExample(); } } ``` 在这个控制器中,我们注入了 `ExampleClient`,并在 `index()` 方法中使用它来调用 example-service 服务的 /example 路径。 4. 运行应用程序 现在,你可以运行应用程序并访问 http://localhost:8080/ ,你应该会看到来自 example-service 服务的响应。 这就是一个使用 `feign-spring-mvc-starter` 的示例。使用 `feign-spring-mvc-starter`,你可以更方便地使用 Feign 来调用 REST 服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值