@EnableFeignClients详解
@EnableFeignClients介绍
/**
* 扫描feignClient修饰的接口,配置在配置类上即可生效
* 支持接口、类以及美剧
*/
@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导入了FeignClientsRegistrar类,FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,负责进行Feign的注入。
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing @Configuration class.
* 根据使用者配置类的注解元数据注册bean定义
* @param importingClassMetadata 使用者配置类的注解元数据
* @param registry 当前bean定义注册表,一般指当前Spring应用上下文对象,当前Spring容器
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
FeignClientsRegistrar的实现是
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
// 注册配置到容器 registry
registerDefaultConfiguration(metadata, registry);
// 注册所发现的各个 feign 客户端到到容器 registry
registerFeignClients(metadata, registry);
}
第一步 注册配置
registerDefaultConfiguration方法
// 注册feign客户端的缺省配置,缺省配置信息来自注解元数据的属性 defaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
// 获取注解@EnableFeignClients的注解属性
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
// 下面是对所注册的缺省配置的的命名,格式如下 :
// default.xxx.Application
if (metadata.hasEnclosingClass()) {
// 针对注解元数据metadata对应一个内部类或者方法返回的方法本地类的情形
name = "default." + metadata.getEnclosingClassName();
}
else {
// name 举例 : default.xxx.Application
// 这里 xxx.Application 是注解@EnableFeignClients所在配置类的全路径限定名
name = "default." + metadata.getClassName();
}
// 各种信息准备就绪,现在执行注册
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
// 将指定feign客户端配置configuration作为一个bean定义注册到容器:
// bean 定义对象类型 : GenericBeanDefinition
// bean class : FeignClientSpecification
// bean name : default.xxx.Application.FeignClientSpecification (缺省配置)
// bean name : xxxservice.FeignClientSpecification (针对某个feign client 的配置)
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
// 设置构造函数参数
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 从bean定义构建器构造bean定义并注册到容器
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
第二步 注册Feign
public 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);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
//没有配置client就根据其他配置获取路径
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
// 使用client配置的类
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
//扫描的类需要满足类名 在 clients里
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)));
}
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);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
注册的具体逻辑,这里主要是填充参数,解析springEL表达式也会在这里进行。
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);
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);
}
解析name、path、url时会做一些特殊处理。name和url会拼接http,path会生成/path格式。
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;
}
static String getUrl(String url) {
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
}
try {
new URL(url);
}
catch (MalformedURLException e) {
throw new IllegalArgumentException(url + " is malformed", e);
}
}
return url;
}
static String getPath(String path) {
if (StringUtils.hasText(path)) {
path = path.trim();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
}
return path;
}
最后注册出来的Feign是ReflectiveFeign$FeignInvocationHandler代理对象,其上没有FeignClient注解
参考资料
https://blog.csdn.net/andy_zhang2007/article/details/86680622
第一步的内容均转载自上文。