Openfeign源码浅析

文章目录

Openfeign源码浅析

Openfeign用途

  • Openfeing是一个声明式的一个http客户端,我们只需要在接口上加个@FeignClient注解就可以用于发起http请求。

Openfeign特性

  • 能够结合注册中心通过service服务发现负责均衡的方式发起调用,也能够指定url直接发起调用(如果url是个k8s的svc服务地址k8s会自动负载均衡)
  • 配置能够支持contextId维度隔离(可理解为接口维度),也就是说可以在服务接口维度支持请求超时时间、重试次数、编解码配置、日志级别等隔离,通过NamedContextFactory来实现
  • 能够方法与第三方的高可用件实现请求限流、熔断、降级等,比如与Hystrix、resilience4j、sentinel

Openfeign组件

  • Client:发送http请求的客户端
    • Default:默认实现,用于指定url时使用
    • FeignBlockingLoadBalancerClient:用于结合注册中心负载均衡时使用
    • RetryableFeignBlockingLoadBalancerClient:支持重试,用于结合注册中心负载均衡时使用
    • OkHttpClient:需要引入feign-okhttp依赖
  • Targeter:目标接口,用于支持熔断扩展实现
    • DefaultTargeter:默认实现
    • FeignCircuitBreakerTargeter:支持熔断,需要通过spring.cloud.openfeign.circuitbreaker.enabled开启,并且引入熔断组件,比如:spring-cloud-starter-circuitbreaker-resilience4j
  • Contract:用于解析接口上的方法,解析为方法对应MethodMetadata列表[]
    • SpringMvcContract
  • MethodMetadata:方法的元数据,包含returnType、bodyType、RequestTemplate
    • RequestTemplate:@RequestMapping解析的封装在这,最终会转换为Request(url、method、header、body)
  • MethodHandler:每个接口的方法对应的处理器,包含方法的MethodMetadata、目标对象、client
    • SynchronousMethodHandler:
    • AsynchronousMethodHandler:
  • FeignInvocationHandler:每个方法执行的代理方法,jdk动态代理实现的
    • Map<Method, MethodHandler> dispatch维护了方法和对应的方法处理器
  • Encoder:对象转字节数组,一般是通过Content-Type和HttpMessageConverter来进行编码
    • SpringEncoder
    • SpringFormEncoder
  • Decoder:字节数组转对象,一般是通过Content-Type和HttpMessageConverter来进行解码

代理生成流程

  • org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject
  • org.springframework.cloud.openfeign.DefaultTargeter#target
  • feign.Feign.Builder#target(feign.Target)
  • feign.ReflectiveFeign#newInstance
  • feign.ReflectiveFeign.ParseHandlersByName#apply
    • feign.Contract#parseAndValidateMetadata
  • feign.InvocationHandlerFactory#create
    • FeignInvocationHandler:包含一个Map<Method, MethodHandler> methodToHandler和

源码执行流程

  • FeignAutoConfiguration-》openFeign的自动配置
  • EnableFeignClients-》@Import(FeignClientsRegistrar.class)
  • FeignClientsRegistrar.class -》是个ImportBeanDefinitionRegistrar实现,扫描FeignClient注解的接口
  • FeignClientFactoryBean-》BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
  • 接着看FeignClientFactoryBean.getObject
    • getTarget(); 生成目标对象
    • feign(feignClientFactory);构建feign
  • ReflectiveFeign-》
  • FeignInvocationHandler-》

FeignClient代理生成流程

  • feign.ReflectiveFeign#newInstance
    • 通过contract组件把接口上方法解析为方法元数据列表List
    • 再把List转换为Map<String, MethodHandler>nameToHandler映射,key为接口名+方法名+参数类型,value:一般为SynchronousMethodHandler
    • 在把nameToHandler映射转换为Map<Method, MethodHandler> methodToHandler方法 和方法处理器的映射,为后面执行的时候根据方法找到MethodHandler
    • 在根据methodToHandler创建jdk的动态代理调用处理器 FeignInvocationHandler
java 复制代码
public <T> T newInstance(Target<T> target) {
    //通过contract组件把接口上方法解析为方法元数据列表List<MethodMetadata>,并转换为Map<String, MethodHandler>nameToHandler
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
 
    //在把nameToHandler映射转换为Map<Method, MethodHandler> methodToHandler方法 和方法处理器的映射
    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)));
      }
    }
    //根据methodToHandler创建jdk的动态代理调用处理器 FeignInvocationHandler
    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;
  }

FeignClient接口代理执行流程

  • feign.ReflectiveFeign.FeignInvocationHandler#invoke
    • 通过methodToHandler根据方法找到MethodHandler
    • 执行MethodHandler的调用,feign.SynchronousMethodHandler#invoke
  • feign.SynchronousMethodHandler#invoke
java 复制代码
 public Object invoke(Object[] argv) throws Throwable {
    //编码:通过Encoder组件把方法上的参数编码,与springmvc一致也是通过HttpMessageConverter来进行编码,messageConverter.write
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        //执行调用并把响应解码为我们定义的方法返回值
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        //重试
        retryer.continueOrPropagate(e);
        continue;
      }
    }
  }
 
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);
    Response response;
    try {
        //通过client组件发起http请求
      response = client.execute(request, options);
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
    }
 
 
    if (decoder != null)
        //通过decoder把响应数据解码我们的对象,即:messageConverter.read
      return decoder.decode(response, metadata.returnType());
  }

Openfeign实现

从FeignClient注解开始

  • 我们只在接口上加个@FeignClient注解就能够发起http请求,那么根据以往的经验应该是把FactoryBean注入到spring容器,然后在调用FactoryBean.getObject方法的时候返回一个代理实例。
  • 顺着@FeignClient我们找到是FeignClientsRegistrar(ImportBeanDefinitionRegistrar实现)把含有@FeignClient的接口注册到spring容器,那么我们看看他代码实现
java 复制代码
//org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions
@Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        //注册默认的配置
        registerDefaultConfiguration(metadata, registry);
        //注册FeignClient
        registerFeignClients(metadata, registry);
    }
 
    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
 
        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            //扫描包含FeignClient的类
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            Set<String> basePackages = getBasePackages(metadata);
            for (String basePackage : basePackages) {
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }
 
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
 
                //获取FeignClient注解上的属性值
                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());
 
                String name = getClientName(attributes);
                //注册注解configuration的配置
                registerClientConfiguration(registry, name, attributes.get("configuration"));
                 
                //我们继续关注FeignClient注册
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
 
    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
            Map<String, Object> attributes) {
        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);
        //关键点FeignClientFactoryBean,确实是个FactoryBean
        FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        factoryBean.setBeanFactory(beanFactory);
        factoryBean.setName(name);
        factoryBean.setContextId(contextId);
        factoryBean.setType(clazz);
        factoryBean.setRefreshableClient(isClientRefreshEnabled());
        //把注解属性上的值赋值到FeignClientFactoryBean
        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");
            Object fallbackFactory = attributes.get("fallbackFactory");
            return factoryBean.getObject();
        });
 
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
 
         
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

画图总结

相关推荐
摇滚侠8 分钟前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY31 分钟前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克31 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠2 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌2 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
Agent产品评测局2 小时前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
阿丰资源2 小时前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
呱牛do it3 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
消失的旧时光-19433 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解
NE_STOP4 小时前
Redis--发布订阅命令和Redis事务
java