先看一下@EnableFeignClients有5个属性
@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 {};
}
- value:basePackages()属性的别名。 允许更简洁的注释声明,例如: @ComponentScan(“org.my.pkg”)而不是@ComponentScan(basePackages=“org.my.pkg”) 。返回:‘basePackages’ 数组。
- basePackages,:用于扫描带注释组件的基本包。
value()是此属性的别名。
使用basePackageClasses()作为基于字符串的包名称的类型安全替代方案。
返回:‘basePackages’ 数组。 - basePackageClasses:basePackages()类型安全替代方案,用于指定要扫描带注释组件的包。 将扫描指定的每个类的包。(比如A.class位于包p1下面,那么配置了basePackageClasses = A.class后,A所在的包P1以及子包都会被扫描到)
考虑在每个包中创建一个特殊的无操作标记类或接口,除了被此属性引用外,没有其他用途。
返回:‘basePackageClasses’ 的数组。 - clients都是用来指定扫路径:用@FeignClient 注释的类列表。 如果不为空,则禁用类路径扫描。
返回:FeignClient 类列表 - defaultConfiguration指定feign相关的配置。
我们看到,改注解使用了@import(FeignClientsRegistrar.class)像容器中引入FeignClientsRegistrar类,该类实现了importBeanDefinitionRegistrar方法在spring启动过程中会调用registerBeanDefinitions方法,同时通过实现EnvironmentAware和ResourceLoaderAware接口来获取到项目的resourceLoader和environment。
class FeignClientsRegistrar implements importBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
重点观察registerBeanDefinitions方法分为两个部分:注册默认的config配置以及注册扫描到的所有fenignClients
public void registerBeanDefinitions(Annotationmetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
registerDefaultConfiguration
那么首先一探registerDefaultConfiguration
private void registerDefaultConfiguration(Annotationmetadata metadata,
BeanDefinitionRegistry registry) {
Map 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注解所标注的类上改注解的属性
- 如果该类有直接外部类Class对象(比如匿名内部类的直接外部类就是该内部类的外部类),则使用直接外部类命名,加上前缀default.
- 通过registerClientConfiguration方法注册FeignClientSpecification对象
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
//FeignClientSpecification构造器赋值
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//注册FeignClientSpecification
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
也就是说configuration是作为FeignClientSpecification的一个属性,真正被注入的类型是FeignClientSpecification。
registerFeignClients 获取scannerClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader);
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getmetadata().isIndependent()) {
if (!beanDefinition.getmetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
}
可以看到,这儿使用的scanner的是ClassPathScanningCandidateComponentProvider对象,isCandidateComponent是否成立的条件是beanDefinitions对应的类是top class且不是注解。
处理basepackagesif (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getbasePackages(metadata);
}
else {
final Set 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)));
}
如果没有配置clients,则getbasePackages获取扫描包信息
否则根据getPackageName方法获得clients指定的类所在的包作为扫描路径
protected SetgetbasePackages(Annotationmetadata importingClassmetadata) { Map attributes = importingClassmetadata .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName()); Set basePackages = new HashSet<>(); for (String pkg : (String[]) attributes.get("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : (String[]) attributes.get("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class> clazz : (Class[]) attributes.get("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add( ClassUtils.getPackageName(importingClassmetadata.getClassName())); } return basePackages; }
可以看到,value,basePackages,basePackageClasses与clients的确是互斥的,但他们三个是可以同时生效的,如果都没有配置,则获取当前类的包作为扫描路径。最后在看一下getPackageName方法:简单的截取当前类的包名并返回
public static String getPackageName(String fqClassName) {
Assert.notNull(fqClassName, "Class name must not be null");
int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
}
注册client
for (String basePackage : basePackages) {
Set candidateComponents = 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();
Assert.isTrue(annotationmetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map attributes = annotationmetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
//注册ClientConfiguration
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注册feignClient
registerFeignClient(registry, annotationmetadata, attributes);
}
}
}
1:@FeignClinets只能用来对接口进行注解,否则校验不通过
2:注册@FeignClients中的configuration,name的优先级以此为:contextId,value,name,serviceId
private String getClientName(Mapclient) { if (client == null) { return null; } String value = (String) client.get("contextId"); if (!StringUtils.hasText(value)) { value = (String) client.get("value"); } if (!StringUtils.hasText(value)) { value = (String) client.get("name"); } if (!StringUtils.hasText(value)) { value = (String) client.get("serviceId"); } if (StringUtils.hasText(value)) { return value; } throw new IllegalStateException("Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName()); }
3:registerFeignClient填充feignClint属性
private void registerFeignClient(BeanDefinitionRegistry registry, Annotationmetadata annotationmetadata, Mapattributes) { String className = annotationmetadata.getClassName(); 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); //alias默认是contextId + "FeignClient" String alias = contextId + "FeignClient"; 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; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
alias默认是contextId + “FeignClient”,如果配置了qualifier,则替换为qualifier,需要注意的是,注册到IOC的client是用的alias,要与上文注册configuration的name区分开来
接下来feignClient的name属性的优先级为serviceId,name,value
String getName(Mapattributes) { String name = (String) attributes.get("serviceId"); if (!StringUtils.hasText(name)) { name = (String) attributes.get("name"); } if (!StringUtils.hasText(name)) { name = (String) attributes.get("value"); } name = resolve(name); return getName(name); }
contextId属性如果没有配置,则使用getName方法来获取
如果配置了,则校验host的合法性并返回contextId
private String getContextId(Mapattributes) { String contextId = (String) attributes.get("contextId"); if (!StringUtils.hasText(contextId)) { return getName(attributes); } contextId = resolve(contextId); return getName(contextId); }
static String getName(String name) {
if (!StringUtils.hasText(name)) {
return "";
}
String host = null;
try {
String url;
if (!name.startsWith("http://") && !name.startsWith("https://")) {
url = "http://" + name;
} else {
url = name;
}
host = new URI(url).getHost();
}
catch (URISyntaxException e) {
}
Assert.state(host != null, "Service id not legal hostname (" + name + ")");
return name;
}
值得注意的是,以上方法注册的bean是FeignClientFactoryBean类型,还不是feign最终使用的动态代理对象,但是看名字猜测动态代理对象跟它应该有所关联,在下一篇文章我们将探索FeignClientFactoryBean是怎么创建动态代理的



