2020.0.X版本开始的OpenFeign底层不再使用Ribbon了
FeignClient的bean注册过程

1.1 @FeignClientsRegistrar开启对FeignClient的扫描
此处主流程上无区别;
在SpringBoot启动流程中@FeignClientsRegistrar注解开启OpenFeign的入口、OpenFeign扫描所有的FeignClient的流程 高版本和低版本基本一样,低版本的见文章:OpenFeign源码2-Bean注册过程和调用过程
主要流程如下:
- 开启扫描FeignClient的入口
在启动类上添加注解 @EnableFeignClients来开启Feign 客户端,@EnableFeignClients注解会通过@Import注解导入配置类FeignClientsRegistrar,在SpringBoot启动流程中注入到启动类的ConfigurationClass的属性中,在注册启动类的BeanDefinition时,会遍历调用其@Import的所有ImportBeanDefinitionRegistrar接口的 registerBeanDefinitions()方法;
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。
- 扫描
FeignClient:
拿到@EnableFeignClients注解中配置的扫描包路径相关的属性,得到要扫描的包路径; 获取到扫描器ClassPathScanningCandidateComponentProvider,然后给其添加一个注解过滤器(AnnotationTypeFilter),只过滤出包含@FeignClient注解的BeanDefinition; 扫描器的findCandidateComponents(basePackage)方法从包路径下扫描出所有标注了@FeignClient注解并符合条件装配的接口;然后将其在BeanDefinitionRegistry中注册一下;
详细如下: FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。
然后就会进入 FeignClientsRegistrar# registerBeanDefinitions 。registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClientBeanDeifinition 添加到 spring 容器中.
java
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// todo 将@EnableFeignClients注解中的defaultConfiguration属性注册到一个缓存map
registerDefaultConfiguration(metadata, registry);
// todo 1) 扫描所有@FeignClient注解的接口,即扫描到所有Feign接口
// 2) 将每个@FeignClient注解的configuration属性注册到一个缓存map
// 3) 根据@FeignClient注解元数据生成 FeignClientFactoryBean 的BeanDefinition,
// 并将这个BeanDefinition注册到一个缓存map
registerFeignClients(metadata, registry);
}
-
registerDefaultConfiguration:javaprivate void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 获取到@EnableFeignClients注解的属性值 // 第二个参数true,表示将Class类型的属性变为了String类型 Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) { String name; // 判断当前注解所标注的类是否为闭合类 if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } // todo 注册这个defaultConfiguration属性 registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } } private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { // 生成一个Builder BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(FeignClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); // 获取到构建的BeanDefinition } -
registerFeignClientsjavapublic void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 定义一个扫描器 ClassPathScanningCandidateComponentProvider scanner = getScanner(); // 初始化扫描器 scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; // 获取@EnableFeignClients注解的属性元数据 Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); // 获取clients属性值 final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); // 若clients属性为空,则启用类路径扫描 if (clients == null || clients.length == 0) { // 为扫描器添加一个扫描@FeignClient注解的Filter scanner.addIncludeFilter(annotationTypeFilter); // todo 获取@EnableFeignClients注解中所有指定的基本包 basePackages = getBasePackages(metadata); } else { // 指定了clients属性,将所有指定的Feign接口类添加到集合 final Set<String> clientClasses = new HashSet<>(); basePackages = new HashSet<>(); for (Class<?> clazz : clients) { basePackages.add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { @Override protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter( new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); } // 遍历这些基本包,将其它的@FeignClient接口找到并写入到集合 for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); // 遍历 候选组件 集合(所有Feign接口都在这个集合中) for (BeanDefinition candidateComponent : candidateComponents) { // 只处理Feign接口组件 if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; // 从BeanDefinition中获取注解元数据 AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); // 断言:若不是接口,直接抛出异常 Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); // 获取@FeignClient注解属性 Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); // todo 获取Feign名称 String name = getClientName(attributes); // 将当前遍历的Feign接口的configuration注册到缓存map registerClientConfiguration(registry, name, attributes.get("configuration")); // 将FeignClientFactoryBean的BeanDefinition注册到缓存map registerFeignClient(registry, annotationMetadata, attributes); } } } }registerFeignClient在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。javaprivate void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { // 获取FeignClient接口的类全路径 String className = annotationMetadata.getClassName(); // 生成一个beanFactory,其会为FeignClientFactoryBean生成一些必要的组件 BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url", getUrl(attributes)); definition.addPropertyValue("path", getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); String contextId = getContextId(attributes); definition.addPropertyValue("contextId", contextId); definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = contextId + "FeignClient"; // 获取到FeignClientFactoryBean的beanDefinition AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } // 生成beanDefinition的holder,通过holder可以获取到这个beanDefinition BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); // 把BeanDefinition的这个bean定义注册到IOC容器 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过
genericBeanDefinition来构建的,并且传入了一个FeignClientFactoryBean的类,代码如下。javapublic static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) { BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition()); builder.beanDefinition.setBeanClass(beanClass); return builder; }
综上代码分析,其实实现逻辑很简单。
-
创建一个
BeanDefinitionBuilder。 -
创建一个工厂
Bean,并把从@FeignClient注解中解析的属性设置到这个FactoryBean中 -
调用
registerBeanDefinition注册到IOC容器中
1.2 为FeignClient生成动态代理类
区别主要体现在这里;
在注册FeignClient到Spring容器时,构建的BeanDefinition的beanClas是FeignClientFactoryBean;FeignClientFactoryBean是一个工厂,保存了@FeignClient注解的所有属性值,在Spring容器初始化的过程中,其会根据之前扫描出的FeignClient信息构建FeignClient的动态代理类。详见OpenFeign源码2-Bean注册过程和调用过程
底层通信Client的区别?
在使用Feign.Builder构建FeignClient的时候,获取到的Client是FeignBlockingLoadBalancerClient(这其中的逻辑后面聊,在OpenFeign低版本是LoadBalancerFeignClient);用于生成FeignClient的Targeter是DefaultTargeter(在OpenFeign低版本是HystrixTargeter,高版本移除了Hystrix,采用Spring Cloud Circuit Breaker 做限流熔断);
具体体现在FeignClientFactoryBean#loadBalance()方法,其是一个进行负载均衡的FeignClient动态代理生成方法; 
OpenFeign低版本:

FeignBlockingLoadBalancerClient何时注入到Spring容器?
FeignBlockingLoadBalancerClient注入到Spring容器的方式和OpenFeign低版本的LoadBalancerFeignClient是一样的;
进入到FeignBlockingLoadBalancerClient类中,看哪里调用了它唯一一个构造函数;

找到FeignBlockingLoadBalancerClient发现有三个地方调用了它的构造函数,new了一个实例;
- DefaultFeignLoadBalancedConfiguration
- HttpClientFeignLoadBalancedConfiguration
- HttpClient5FeignLoadBalancerConfiguration
- OkHttpFeignLoadBalancedConfiguration
再结合默认的配置,只有DefaultFeignLoadBalancedConfiguration中的Client符合条件装配;

可以通过引入Apache HttpClient的maven依赖使用HttpClientFeignLoadBalancedConfiguration,
或引入OkHttpClient的maven依赖并在application.yml文件中指定feign.okhttp.enabled属性为true使用OkHttpFeignLoadBalancedConfiguration。
DefaultTargeter在哪里注入到Spring容器?
DefaultTargeter注入到Spring容器的方式和OpenFeign低版本的HystrixTargeter是一样的;
在FeignAutoConfiguration类中可以找到Targeter注入到Spring容器的逻辑; 
后续生成动态代理类的逻辑和旧版本一样
都体现在ReflectiveFeign#newInstance()方法中:

2. Client处理负载均衡(核心区别)
上面提到OpenFeign高版本获取到的Client是FeignBlockingLoadBalancerClient,而低版本的是LoadBalancerFeignClient,LoadBalancerFeignClient基于Ribbon实现负载均衡,FeignBlockingLoadBalancerClient就靠OpenFeign自己实现负载均衡;
接下来浅看一下FeignBlockingLoadBalancerClient是如何做负载均衡的!!
2.1 FeignBlockingLoadBalancerClient选择一个服务实例

參考: