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 }
-
registerFeignClients
javapublic 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选择一个服务实例
參考: