Spring Cloud Feign 分析(一)之FeignCl
当我们使用@FeignClient作为客户端请求方式时,使用起来非常简单,启动类直接标注上@EnableFeignClients注解,然后就可以使用标注了@FeignClient注解的接口类进行请求,但是这个@FeignClient注解背后的原理是怎么样?@FeignClient是如何注册到Spring IOC容器中的?这一节我们带着这些疑问分析@FeignClient的注册过程!
EnableFeignClients
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
一如继往先看看应用入口的配置类@EnableFeignClients这个注解做了什么事情,@Import(FeignClientsRegistrar.class)这个地方看起来就是在注册@FeignClient注解标注的对象,那么我们继续往下看看FeignClientsRegistrar又做了哪些事情。
FeignClientsRegistrar#registerDefaultConfiguration
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
//根据EnableFeignClients注解生成一个默认的Feign客户端配置
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
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();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
}
根据@EnableFeignClients注解中配置的defaultConfiguration属性配置,生成一个FeignClientSpecification对象,name="default." + 应用启动类全名路径
FeignClientsRegistrar#registerFeignClients
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
......
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//满足条件的Bean组件(候选名单集合)
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
//获取EnableFeignClients注解属性
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//获取EnableFeignClients注解中的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);
//设置扫描器需要扫描的注解类型
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
//获取扫描的路径,会从@EnableFeignClients注解中获取相关配置
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
//将扫描器中返回的BeanDefinition集合添加到candidateComponents集合中
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
//如果配置了clients属性,则直接使用
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
//只处理带注解的BeanDefinition
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
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());
//获取@FeignClient注解客户端名称
String name = getClientName(attributes);
//注册FeignClientSpecification,Bean名称为FeignClient客户端名称
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注册@FeignClient的代理对象
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
......
}
从以上的代码片段以及注释信息我们不难看出,注册@FeignClient注解的过程比较复杂,大致分为了以下步骤:
- 获取@EnableFeignClients注解中的clients属性
- 如果配置了clients属性值则直接加入到candidateComponents这个候选名单集合中
- 如果没有获取到clients配置,则开始创建扫描器
- 设置扫描器资源加载属性、扫描器需要扫描的注解类型(@FeignClient注解)
- 从@EnableFeignClients注解属性中获取扫描的路径
- 根据扫描路径创建BeanDefinition集合并加入到候选名单集合中
- 根据@FeignClient注解中的客户端名称创建FeignClientSpecification
- 生成@FeignClient注解的代理对象
从registerFeignClients这个注册步骤中就能看见步骤多,而且复杂,其中在第八步中还需要创建@FeignClient的代理对象,那么我们就继续聊聊这个创建代理对象的过程!
FeignClientsRegistrar#registerFeignClients
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
......
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
//获取@FeignClient注解对应的类名
String className = annotationMetadata.getClassName();
//转换为Class对象
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
//获取上下文id,其实就是获取的@FeignClient注解的name、value属性值
String contextId = getContextId(beanFactory, attributes);
//获取的@FeignClient注解的name、value属性值
String name = getName(attributes);
//创建FeignClientFactoryBean代理对象
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
//设置beanFactory、Name、ContextId、ClassType
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
//初始化BeanDefinitionBuilder,并设置fallback降级策略
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();
});
//AutowireMode类型
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
//懒加载模式
definition.setLazyInit(true);
//校验fallback设置
validate(attributes);
//别名
String alias = contextId + "FeignClient";
//返回BeanDefinition定义
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
//是否为主Bean
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
//获取@FeignClient是否设置了别名属性,若设置了就使用
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
//注册代理对象返回的BeanDefinition到Spring IOC容器
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
......
}
生成一个代理对象,我们发现好像更复杂了,步骤也特别多了,这里也总结下大致的步骤:
- 获取@FeignClient注解对应的类名并转换为Class类对象
- 获取@FeignClient注解的name、value属性值
- 创建FeignClientFactoryBean代理对象并设置beanFactory、Name、ContextId、ClassType参数
- 创建BeanDefinitionBuilder对象并设置factoryBean代理对象的Url、Path、FallBack参数,并返回代理对象
- 设置BeanDefinitionBuilder对象为懒加载模式
- 通过BeanDefinitionBuilder获取beanDefinition对象并设置beanDefinition相关的属性,如对象类型className,是否为主Bean
- 注册代理对象返回的BeanDefinition到Spring IOC容器中,如果设置了别名则使用@FeignClient注解中的别名值
这一章节我们讲解了FeignClient注册过程,这个过程中会为每一个@FeignClient对象都生成一个代理类对象,步骤比较多,这里也只是讲解了一部分,比如在factoryBean.getObject()这个返回代理对象的具体逻辑中还会涉及到Hystrix熔断、LoadBalance负载均衡相关的,这些逻辑会放在后面调用过程中进行讲解!