注解属性
先看一下@EnableFeignClients有5个属性
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
*
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
*
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
* @return list of FeignClient classes
*/
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相关的配置。
FeignClientsRegistrar
我们看到,改注解使用了@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<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注解所标注的类上改注解的属性
- 如果该类有直接外部类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
获取scanner
ClassPathScanningCandidateComponentProvider 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且不是注解。
处理basepackages
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
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)));
}
如果没有配置clients,则getBasePackages获取扫描包信息
否则根据getPackageName方法获得clients指定的类所在的包作为扫描路径
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
Set<String> 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<BeanDefinition> 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<String, Object> 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(Map<String, Object> client) {
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, Map<String, Object> attributes) {
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
/* for testing */ String getName(Map<String, Object> attributes) {
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(Map<String, Object> attributes) {
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是怎么创建动态代理的