Feign源码详解

一,入口 —— Feign的核心注解

Feign是我们在分布式开发中常用的RPC框架,关于Feign远程调用的秘密,我有很多想要探究的例如:

  • Feign是如何收集FeignClient的?
  • Feign是如何配置FeignClient的,让其拥有降级重试的能力?
  • Feign整个远程调用的流程是怎么样的?
  • Feign他是如何创建代理类的?
  • Feign他是怎么去将一个Interface里面的接口方法作为请求发送的?
  • Feign是如何兼容这么多HttpClient框架的?
  • Feign他是怎么做到负载均衡的,如何和Ribbon结合的?

这么多繁杂的问题,往往扰人心智,不如我们从我们开发中最常见最常用的两个注解入手。

①,@EnableFeignClients

这个注解通常标注在启动类上,作用看起来和@SpringBootApplication一样,我们姑且猜测他的职能也和@SpringBootApplication一样是负责开启Feign功能以及扫描相关类,我们直接进入@EnableFeignClients来探究源码。

@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
    // 根据包路径扫描其下的FeignClient,类似于basePackages的简写
    String[] value() default {};

    // 根据包路径扫描其下的FeignClient
    String[] basePackages() default {};

    // 指定标记的接口来扫描包
    Class<?>[] basePackageClasses() default {};

    // Feign客户端默认的全局配置类
    Class<?>[] defaultConfiguration() default {};

    // 指定@FeignClient注解的类,直接使用这几个@FeignClient,此时会ban掉类路径扫描
    Class<?>[] clients() default {};
}

可以通过源码和注释看出来,@EnableFeignClients的作用是用来扫描@FeignClient标注的类和指定全局配置类,值得注意的是在该注解最上方还有一个

@Import({FeignClientsRegistrar.class})

在我们使用@EnableFeignClientsFeignClientsRegistrar也会被引入至SpringBean容器的上下文。从名称可以看出这是FeignClient的注册类,我们再大胆猜测一下,FeignClientsRegistrar该类的职能应该是扫描和注册FeignClients,他肯定是接下来我们源码阅读的关接类之一,再次我们现在这里将其Mark一下,稍后回来。

②,@FeignClient

对于这个接口我们通常是标记在一个远程调用RPC的Interface类上面,但是其作用是用来标记还是用来声明呢,这个作用其实光从名称很难理解,我们可以看看作者留给我们的注释来熟悉一二:

注解用于声明某接口应创建为 REST 客户端(例如,用于自动注入到其他组件中)。如果 SC LoadBalancer 可用,它将用于对后端请求进行负载均衡,且负载均衡器可以使用与 Feign 客户端相同的名称(即 value)进行配置。

看完注释我们再结合代码来进行理解:

public @interface FeignClient {

	@AliasFor("name")
    // 服务名称 (name的简化版)
	String value() default "";
    
	// 接口生成的动态代理的bean Id
	String contextId() default "";

	@AliasFor("value")
     // 服务名称 (name的简化版)
	String name() default "";
    
	String[] qualifiers() default {};

    // 服务的url,对于开启了Loadbalance的服务无需使用url,如果没有开启则需要一个绝对的地址
	String url() default "";

    // 如果为false则404时默认抛出 FeignException异常,为true则抛出404异常
	boolean decode404() default false;

    // Feign客户端本身的配置类,可以对 feign.codec.Decoder, feign.codec.Encoder, feign.Contract.等等进行自定义
	Class<?>[] configuration() default {};

    // 失败回调方法
	Class<?> fallback() default void.class;
	
    // 用于生成fallback类实例
	Class<?> fallbackFactory() default void.class;
	
    // 定义当前FeignClient的统一前缀
	String path() default "";

	boolean primary() default true;

}

看完源码和注释后,我们可以总结出FeignClient的功能:

用来声明FeignClient的一些基本属性,例如:bean name,url,服务名称,path等等

对FeignClient的一些核心功能组件进行配置,核心配置组件如下图所示(截图取自 bojiangzhou 大佬)

image

③ , Feign客户端配置和全局配置

看到上面两个主即我们可以发现,不管是@EnableFeignClients@FeignClient 都有配置项选择,那对于配置项我们该怎么使用呢

1,yaml配置项

feign:
 client:
  config:
   # 默认全局配置
   default:
   # 指定客户端名称
   genius-client:

2,代码配置项

public class GeniusFeignConfiguration {

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default();
    }
}
@FeignClient(value = "genius-client", configuration = GeniusFeignConfiguration.class)
public interface GeniusFeignClient{
    
}

可以看到对于客户端配置和全局配置的使用都很简单方便,但是值得注意的一点是这两个的配置项使用优先级是不同,这个我们后续再谈。

本章导图

image

二,FeignClientsRegister

我们重新回到之前mark的FeignClientsRegister先来看看整个类的实现

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware{
    
    private ResourceLoader resourceLoader;
    
    private Environment environment;
}

可以看到FeignClientsRegister实现了三个接口

  • ResourceLoaderAware:注入资源加载器 ResourceLoader
  • EnvironmentAware:注入环境 Environment
  • ImportBeanDefinitionRegistrar:注册并注入BeanDefinition

BeanDefinition 实际上是Spring bean的元数据,它保存了Bean的很多属性,我们通常注册BeanDefinition的方式有 @Component,@Bean等。而实现ImportBeanDefinitionRegistrar也是一种注册BeanDefinition 的方式,它通过registerBeanDefinitions 方法来实现BeanDefinition 的注册。

通过这三个实现的接口,我们也找到了核心函数 registerBeanDefinitions,我们来看看他的代码:

/**
 * @param metadata @EnableFeignClients 注解的元数据
 * @param registry BeanDefinition 注册器
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 注册默认配置项
    registerDefaultConfiguration(metadata, registry);
    // 注册FeignClients
    registerFeignClients(metadata, registry);
}

可以看出整个BeanDefinitions的注册分为两个步骤:注册默认配置项注册FeignClients

① 注册默认配置项

这一步是将@EnableFeignClients上的defaultConfiguration内容,给注册为BeanDefinitions,如果不存在则不注册

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 获取注解上的属性信息
   Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
	
    // defaultConfiguration不为空则注入自定义默认配置项
   if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
      String name;
       // 查看注解是不是标记在内部类上
      if (metadata.hasEnclosingClass()) {
         name = "default." + metadata.getEnclosingClassName();
      }
      else {
         name = "default." + metadata.getClassName();
      }
       // 注册自定义默认配置项
      registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
   }
}

② 注册FeignClient

registerFeignClients方法 分为两大部分 扫描包注册FeignClient 我们接下来进入源码一一介绍:

1,扫描包找到所有FeignClient,放入候选队列

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");
// 不存在则启用扫描器,扫描包下的FeignClient
if (clients == null || clients.length == 0) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
    // 设置扫描注解类型为 FeignClient.class
    scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
    // 获取注解中所有待扫描的包路径,value,basePackages,basePackageClasses,都不存在则添加EnableFeignClients标记对象所在的包路径
    Set<String> basePackages = getBasePackages(metadata);
    for (String basePackage : basePackages) {
        // 使用扫描器扫描并放入候选队列
        candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
    }
}
else {
    for (Class<?> clazz : clients) {
        // 将clients项放入候选队列
        candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
    }
}

从上述我们可以明白Feigjn是如何获取项目中的所有FeignClient:

  • clients值不存在时,扫描包路径:value,basePackages,basePackageClasses 或者 (前面配置项都不存在时)EnableFeignClients标记对象所在的包路径
  • clients值存在时,直接使用clients中的值

2,校验并注册候选队列中的所有FeignClient标记的接口

for (BeanDefinition candidateComponent : candidateComponents) {
    // 对象是否是注解Bean
    if (candidateComponent instanceof AnnotatedBeanDefinition) {
        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());
	   // 获取Client配置名称,优先级如下:contextId, valueId, name, serviceId
        String name = getClientName(attributes);
        // 注册Client服务配置,名称为Client配置名称
        registerClientConfiguration(registry, name, attributes.get("configuration"));
	    // 注册FeignClient
        registerFeignClient(registry, annotationMetadata, attributes);
    }
}

经过简单的验证和服务配置注册后我们来到核心方法registerFeignClient(registry, annotationMetadata, attributes)

3,registerFeignClient——注册FeignClient核心方法

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);
    	// 获取名称 优先级 serviceId(弃用),name,value,并检验 http://+name是否合法
		String name = getName(attributes);
    
    	// 构建FeignClientFactoryBean 工厂,职责是Feign创建代理类的工厂
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
    
    	// 设置构建Url,Path,404,fallback和fallbackFactory并返回代理类的supplier,clazz为注解的接口对象
		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);
    	// 校验fallback和fallbackFactory是否争取(放在这里校验是否有点,早期版本是放在前面的,不太明白为什么设置写完了supplier才校验)
		validate(attributes);
		
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

		// 设置优先级
		boolean primary = (Boolean) attributes.get("primary");
		beanDefinition.setPrimary(primary);
		
    	// 设置bean别名
		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}
		// 将信息放入BeanDefinitionHolder
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    	// 注册Bean
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
		
		registerOptionsBeanDefinition(registry, contextId);
	}

可以看到这里有个很重要的类 FeignClientFactoryBean,它实际上是实现了FactoryBean 接口,这个接口是Spring提供的一个工厂接口

public interface FactoryBean<T> {
    @Nullable
    T getObject() throws Exception;
}

它里面提供了一个方法,用于获取工厂中所提供的Bean,当你实现并注册这个类时,获取该Bean名称获取的实际上是调用了 factory.getObject() 来获取其中的类对象。

FeignClientFactoryBean 它是创建FeignClient 代理类的工厂,他在后续的代理类生成中至关重要,我们在后面一章中来详细介绍他。

本章导图

image

三,FeignClientFactoryBean

在上一节我们知道FeignClientFactoryBean实现了FactoryBean,通过getObject()方法生成FeignClient,我们来详细看看他的方法实现

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

<T> T getTarget() {
    
    //1,配置Feign.Builder
    FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
        : applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);

    //2.1,未设置url,则创建loadBalance Feign
    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();
        //返回具备负载均衡能力的Target
        return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
    }
    
    //2.2,设置了url,则创建具体的 FeignClient
    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) {
            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
        }
        if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
            client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
        }
        builder.client(client);
    }
    // 创建动态代理对象Target
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

我们可以看到FeignClientFactoryBean创建FeignClient对象分为两个步骤:构建Builder和创建不同的代理对象

1,Feign.Builder构建和设置配置项

在构建Feign.Builder前,他会先获取整个Feign配置的上下文环境,也就是FeignContext。之后他便会定制Encoder,Dcoder,Contract等组件的Feign Builder模板,通过一些配置代码和文件进行Builder的设置。

protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(type);

		// 定制Encoder,Dcoder,Contract等组件的Feign Builder模板
		Feign.Builder builder = get(context, Feign.Builder.class)
				.logger(logger)
				.encoder(get(context, Encoder.class))
				.decoder(get(context, Decoder.class))
				.contract(get(context, Contract.class));
		// 通过配置代码和配置项初始化Feign.Builder
		configureFeign(context, builder);
		applyBuildCustomizers(context, builder);

		return builder;
	}

通过下面我们可以知道整个FeignClient的一个配置优先级如下:

自定义服务配置项 > 默认配置项 > 代码配置Config配置

protected void configureFeign(FeignContext context, Feign.Builder builder) {
   FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
         : applicationContext.getBean(FeignClientProperties.class);

   FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
   setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());

   if (properties != null && inheritParentContext) {
      if (properties.isDefaultToProperties()) {
          // 低优先级,代码配置Config配置
         configureUsingConfiguration(context, builder);
          // 中优先级,默认配置项 feign.client.default
         configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
          // 高优先级,自定义服务配置项 feign.client.<clientName>
         configureUsingProperties(properties.getConfig().get(contextId), builder);
      }
	  //.......
   }
   else {
       //通过开关优先使用代码配置项
      configureUsingConfiguration(context, builder);
   }
}

2.1,创建loadBalance Feign

在了解LoadBalance时我们先了解一下Client接口,Client接口它有许多HttpClient实现类,他的功能就是将request发出然后将返回值封装进入Response中

public interface Client {
    Response execute(Request var1, Request.Options var2) throws IOException;
    ...
}

他的实现类如下,他会通过一些自动配置类(10.12移除了自动配置类,需要自行构建Bean),来使用其他框架的HttpClient,例如OkHttp,ApacheHttp等等,他有一个默认实现的即Client.Default

image

可以看到Client.Default中包含了一些SSL和域名校验的配置,可以通过自行配置Default来构造Feign Https链路。

public static class Default implements Client {
    private final SSLSocketFactory sslContextFactory;
    private final HostnameVerifier hostnameVerifier;
    private final boolean disableRequestBuffering;

    public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
        this.sslContextFactory = sslContextFactory;
        this.hostnameVerifier = hostnameVerifier;
        this.disableRequestBuffering = true;
    }
}

再回到loadbalance方法我们可以发现,Client或根据当前使用HttpClient框架选择注入不同的RibbonClient,并将client放入Fegin.Builder中来生成代理对象Targeter

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
    // 获取Client
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);
       // 生成代理对象
      Targeter targeter = get(context, Targeter.class);
      return targeter.target(this, builder, context, target);
   }

   throw new IllegalStateException(
         "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
}

四,生成代理对象

我们可以看到DefaultTargeter 会使用 FeignBuilder的target()方法生成代理对象

class DefaultTargeter implements Targeter {

   @Override
   public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
         Target.HardCodedTarget<T> target) {
      return feign.target(target);
   }

}

在进行Feign的一系列组件配置后,创建ReflectiveFeign来进行动态代理Feign创建

public <T> T target(Target<T> target) {
    return this.build().newInstance(target);
}

public Feign build() {
     // Feign Http调用客户端,默认为 Client.Default
    Client client = (Client)Capability.enrich(this.client, this.capabilities);
    // 重试器
    Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
    // Feign 请求拦截器
    List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
        return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
    }).collect(Collectors.toList());
    // 日志组件,默认slf4j
    Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
    // 接口协议
    Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
    // 配置类
    Request.Options options = (Request.Options)Capability.enrich(this.options, this.capabilities);
    // 编码器
    Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
    // 解码器
    Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
     // 创建 InvocationHandler 的工厂类
    InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
    QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
    // 接口方法处理器工厂
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
    // 解析 springmvc 注解 
    ReflectiveFeign.ParseHandlersByName handlersByName = new ReflectiveFeign.ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
    // ✨ 动态创建Feign对象
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

ReflectiveFeign.newInstance()会通过InvocationHandlerFactory.create()来进行动态代理创建InvocationHandler

public <T> T newInstance(Target<T> target) {
    //通过contract(即SpringMvcContract)将FeignClient接口转换成MethodHandler
  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 handler = factory.create(target, methodToHandler);
    // 用 Proxy 创建动态代理,动态代理对象就是 SynchronousMethodHandler
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
      new Class<?>[] {target.type()}, handler);

  for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}

他的默认实现是InvocationHandlerFactory.Default然后创建一个ReflectiveFeign.FeignInvocationHandler

public static final class Default implements InvocationHandlerFactory {
    public Default() {
    }

    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
        return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
}

五,一图流总结

🎉特别感谢( bojiangzhou 大佬)🎉

image

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值