Feign 实现原理

Feign是申明式的 HTTP 客户端。代码中创建一个接口并加上@FeingClient 注解即可使用。其底层封装了 HTTP 客户端构建并发送的复杂逻辑。同时也可以整合注册中心及 Ribbon 为其提供负载均衡能力;通过整合 Histrix/sentinal 实现熔断限流功能。本期主要分享下 Feign 与 SpringCloud 的整合过程,及其底层 HTTP 调用的实现细节。

https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_openfeign

使用

  1. pom.xml 中添加

    <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 启动类添加 @EnableFeignClients注解

    @SpringBootApplication(scanBasePackages = {"com.xxx"})
    // 指定 FeiginClient 扫描路径
    @EnableFeignClients(basePackages = {"com.xxx.biz.feign"})
    public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
    }
    
  3. 申明 FeignClient

    @FeignClient(name = "inverter-data-service", url = "${feign.property.inverter-data-service.url}", configuration = {xxx})
    public interface InverterDataFeign {@PostMapping(value = "/api/inverter/get_first_date")FirstDataResponse firstData(@RequestBody String sn, @RequestHeader("token") String token);
    }
    
  4. 服务调用

    @Service
    public class DemoService {@Autowiredprivate InverterDataFeign inverterDataFeign;public void remoteCall(String sn, String token) {// ...FirstDataResponse firstDataResponse = inverterDataFeign.firstData(sn, token);// ...}
    }
    

思考

  • @FeignClient注解是如何使普通的 Java 接口具有 HttpClient 能力的?
  • @FeignClient并非 @Component注解,为什么可以像其他Spring 组件一样注入到其他组件中(如 DemoService)?

实现流程图

实现细节

扫描 & 注册

启动类 Application 申明了注解 @EnableFeignClients,而@EnableFeignClients又申明了@Import(FeignClientsRegistrar.class)FeignClientsRegistrar实现了 ImportBeanDefinitionRegistrar

public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);}/*** 子类实现*/default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}

当 Spring IOC 容器启动时,会去扫描 @ComponentScan 指定包下所有 @Import 的注解,如果 @Import 的 value 参数是 ImportBeanDefinitionRegistrar 的实现类,就会调用 ImportBeanDefinitionRegistrar#registerBeanDefinitions() 方法,而 FeignClientsRegistrar 就是 ImportBeanDefinitionRegistrar 的实现类;故容器初始化过程中 FeignClientsRegistrar#registerBeanDefinitions() 会被执行,这个方法会做两件事情,1. 注册 @EnableFeignClients 中 configuration 参数指定的默认配置类;2. 扫描并注册子包下被 @FeignClient 注解的 feign 客户端

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 注册默认配置类registerDefaultConfiguration(metadata, registry);// 扫描注册 feign 客户端至 spring 容器registerFeignClients(metadata, registry);}
}

下面主要看registerFeignClients(metadata, registry)的实现

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();// 获取 @EnableFeignClients 中所有的参数Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());// 拿到 clients 参数final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");if (clients == null || clients.length == 0) {/* 如果未设置 clients 参数,则去子包下扫描所有注解类型为 @FeignClient 的类 */// 获取扫描器ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);// 设置扫描器的过滤条件,只扫描 @FeignClient 类型scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));// 拿到 @FeignClient 的 basePackages 参数Set<String> basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {// 执行扫描,构建并添加 BeanDefinition 实体candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}}else {// 如果 clients 参数不为空,则只去注册指定的 FeignClientfor (Class<?> clazz : clients) {candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));}}/* 遍历 BeanDefinitions(申明 @FeignClient 的类定义)1. 注册 @FeignClient 中 configuration 参数指定的配置类2. 注册具体的 FeignClient */for (BeanDefinition candidateComponent : candidateComponents) {// 查看组件是否是包含注解的 BeanDefinitionif (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;// 拿到类的注解元数据(包含 class 信息)AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();// 校验,@FeignClient 只能申明在接口上Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");// 拿到 @FeignClient 的属性数据Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());// 获取客户端名称,依次查看 contextId、value、name、serviceId,只要有值就返回String name = getClientName(attributes);// 拿到 configuration 参数,注册配置类registerClientConfiguration(registry, name, attributes.get("configuration"));// 注册 FeignClientregisterFeignClient(registry, annotationMetadata, attributes);}}}

接着看registerFeignClient(registry, annotationMetadata, attributes)的逻辑:

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {// 拿到接口类名,构建 class 对象String className = annotationMetadata.getClassName();Class clazz = ClassUtils.resolveClassName(className, null);ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory? (ConfigurableBeanFactory) registry : null;String contextId = getContextId(beanFactory, attributes);String name = getName(attributes);// 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(isClientRefreshEnabled());// 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {factoryBean.setUrl(getUrl(beanFactory, attributes));factoryBean.setPath(getPath(beanFactory, attributes));factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));Object fallback = attributes.get("fallback");if (fallback != null) {factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback: ClassUtils.resolveClassName(fallback.toString(), null));}Object fallbackFactory = attributes.get("fallbackFactory");if (fallbackFactory != null) {factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory: ClassUtils.resolveClassName(fallbackFactory.toString(), null));}return factoryBean.getObject();});definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);definition.setLazyInit(true);validate(attributes);// 设置 BD 的一些属性,是否 primary,别名等...AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);// has a default, won't be nullboolean primary = (Boolean) attributes.get("primary");beanDefinition.setPrimary(primary);String[] qualifiers = getQualifiers(attributes);if (ObjectUtils.isEmpty(qualifiers)) {qualifiers = new String[] { contextId + "FeignClient" };}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);// 注册 FeignClient 对应的 BeanDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);// 使用 RefreshScope 创建 Request.Options(请求超时时间等配置) BeanDefinitionregisterOptionsBeanDefinition(registry, contextId);}

至此,Spring 容器启动过程中,扫描 FeignClient 并注册到容器的流程已经结束

初始化代理类

注册到 Spring 容器中的 BeanDefinition 会存入一个 Map 中(BeanDefinitionMap);所有 BeanDefinition 注册完毕后,Spring 会对其进行初始化,即实例化并存入 Spring 容器;注册 FeignClient 时,FeignClient 被包装成 FeignClientFactoryBean(FactoryBean 实现类)存入 BeanDefinitionMap;故初始化 FeignClientFactoryBean 时,会回调其 getObject() 方法

再来看刚刚注册 FeignClient 的逻辑:

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {// 省略前面逻辑...// 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(isClientRefreshEnabled());// 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {// 初始化参数factoryBean.setUrl(getUrl(beanFactory, attributes));factoryBean.setPath(getPath(beanFactory, attributes));factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));Object fallback = attributes.get("fallback");if (fallback != null) {factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback: ClassUtils.resolveClassName(fallback.toString(), null));}Object fallbackFactory = attributes.get("fallbackFactory");if (fallbackFactory != null) {factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory: ClassUtils.resolveClassName(fallbackFactory.toString(), null));}// 主要逻辑在这个方法里return factoryBean.getObject();});// 省略后面逻辑...}

创建 FeignClient 对象的逻辑均在 FeignClientFactoryBean#getObject() 中:

 @Overridepublic Object getObject() {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(FeinContext 是在 FeignAutoConfiguration 中通过 @Bean 注入的)// 属于 ApplicationContext 的子容器FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);// 从容器中获取 feign builder 对象(该对象也是在 FeignAutoConfiguration 中通过 @Bean 注入的)Feign.Builder builder = feign(context);if (!StringUtils.hasText(url)) {if (url != null && LOG.isWarnEnabled()) {LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing.");}else if (LOG.isDebugEnabled()) {LOG.debug("URL not provided. Will use LoadBalancer.");}if (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();// 负载均衡,需结合 ribbon 使用return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));}if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}String url = this.url + cleanPath();Client client = getOptional(context, Client.class);if (client != null) {if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}// 容器中获取 Targeter 实例(DefaultTargeter)Targeter targeter = get(context, Targeter.class);// 获取代理对象return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));}

DefaultTargeter#target 会去实例化 ReflectiveFeign, 并调用其 newInstance() 方法实例化代理对象,ReflectiveFeign#newInstance() 逻辑:

public <T> T newInstance(Target<T> target) {// feign 中调用的方法名 -> SynchronousMethodHandler映射;(SynchronousMethodHandler 是 InvocationHandler 的封装,包含 Feign 请求的配置信息) 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)) {DefaultMethodHandler handler = new DefaultMethodHandler(method);defaultMethodHandlers.add(handler);methodToHandler.put(method, handler);} else {methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}// 创建 InvocationHandler 对象InvocationHandler handler = factory.create(target, methodToHandler);// JDK 动态代理,创建代理对象,T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;}

至此,代理对象创建结束.

发送请求

当 FeignClient 发起 http 请求时,会从容器中获取对应的代理类,并调用 FeignInvocationHandler#invoke() 方法,其最终实现在 SynchronousMethodHandler#invoke() 中:

public Object invoke(Object[] argv) throws Throwable {// 解析请求参数,封装 RequestTemplateRequestTemplate template = buildTemplateFromArgs.create(argv);// 获取 Request.Options 对象,封装了请求的 connectTimeout/readTimeout 等信息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() 方法:

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = targetRequest(template);// 打印日志if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();try {// 执行请求,拿到响应response = client.execute(request, options);response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {// ...}// ...
}

如果未设置 http 客户端,则使用默认的客户端 Client.Default,即构建 java.net.HttpURLConnection 并发送请求:

 public Response execute(Request request, Options options) throws IOException {// 构建 java.net.HttpURLConnection 并发送请求HttpURLConnection connection = convertAndSend(request, options);return convertResponse(connection, request);}

问题

实例化 FeignClient 时,Feign 为什么要使用 FeignContext 来给每个 FeignClient 创建一个子IOC容器,如果直接使用父容器会有什么问题?

Feign 的实现原理相关推荐

  1. Feign的调用原理及其源码分析

    Feign的调用原理及其源码分析 目录 概述 架构特性 设计思路 实现思路分析 Feign是如何进行服务调用的 拓展实现 相关工具如下: 实验效果:(解决思路) 分析: 小结: 参考资料和推荐阅读 L ...

  2. SpringCloud——Feign实例及原理

    一.实例 1.配置feign 添加依赖 在maven的pom中添加feign <dependency><groupId>org.springframework.cloud< ...

  3. feign扫描_feign原理+源码解析

    1 架构图. 2 feign的初始化扫包原理. (1)feign使用,是main上的@EnableFeignClients 和 api的@FeignClient(value = "servi ...

  4. springCloud Feign的实现原理

    一.Feign介绍 Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求. Spring Cloud Feign是基于Netflix feign实现,整合了Spr ...

  5. Feign的使用及原理剖析

    feign使用及原理剖析 一.简介 Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求.Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参 ...

  6. Feign底层原理分析-自动装载动态代理

    本篇文章仅介绍Feign的核心机制,包括如何交由Spring容器托管.动态代理机制等内容,不会过分深入细节. 1.什么是Feign? 这里套用Feign官方Github上的介绍:"Feign ...

  7. Hystrix、Feign技术底层实现原理

    一. Feign的设计原理 1.1 Feign是什么 Feign 的英文表意为"假装,伪装,变形", 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请 ...

  8. Feign原理及其使用

    场景 当使用多服务时,经常会遇到服务之间的相互调用. 一个服务如果要调用另一个服务的接口,需要: ① 定义一个请求,并设置目标地址. ② 为这个请求设置参数. ③ 为这个请求设置请求头等属性. ④ 发 ...

  9. 关于Feign的几个问题

    本文来说下关于Feign 的几个问题 文章目录 概述 什么是 Feign 什么是 Open Feign Feign 和 Openfeign 的区别 Starter Openfeign 环境准备 生产者 ...

最新文章

  1. Keil MDK 中利用串口及c标准库函数printf为cortex-m3做调试输出(lpc1788)
  2. 自学计算机科学CS总结-by 要有光LTBL
  3. apache+mod_wsgi+django的环境配置
  4. Google和IMAX放弃VR相机
  5. Hibernate查询之Criteria查询
  6. Python学习16 正则表达式3 练习题
  7. Transposed Convolution 反卷积
  8. 架构,改善程序复用性的设计~第五讲 复用离不开反射和IOC
  9. 华为厉害了:已启动6G网络技术研究
  10. 零基础、非计算机相关专业的如何转型程序员
  11. mysql中如何迁移数据文件,迁移mysql数据文件存放位置
  12. Java基础编程题(API阶段测试)
  13. ubuntu 20.10 安装万能五笔(ibus模式)
  14. 创业19年的湖南竞网如何拥抱数字化转型,按下成长加速键?
  15. 我们雇佣了一只大猴子
  16. Global Round 16D2. Seating Arrangements (hard version)(模拟,贪心)
  17. Va02 修改数量和价格条件时报错
  18. 【译】eBPF 概述:第 4 部分:在嵌入式系统运行
  19. DDSM多区域标注之处理overlay文件框出病灶区域
  20. 大数据华而不实么?大数据的本质是什么?

热门文章

  1. linux汇编伪指令大全,ARM汇编伪指令
  2. 【微信小程序】被面试官问的Java问题难倒了
  3. Git 常用指令大全
  4. vscode 优化.vscode/ipch(解决格式化失效以及占用存储空间大的问题)
  5. int型与double型
  6. 企业产品服务市场调查方法
  7. The Warriors []
  8. matlab 结构体
  9. The constructor being called isn‘t a const constructor.
  10. Python 常用库