栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Feign源码之@EnableFeignClients

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Feign源码之@EnableFeignClients

注解属性

先看一下@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 {};
}
  1. value:basePackages()属性的别名。 允许更简洁的注释声明,例如: @ComponentScan(“org.my.pkg”)而不是@ComponentScan(basePackages=“org.my.pkg”) 。返回:‘basePackages’ 数组。
  2. basePackages,:用于扫描带注释组件的基本包。
    value()是此属性的别名。
    使用basePackageClasses()作为基于字符串的包名称的类型安全替代方案。
    返回:‘basePackages’ 数组。
  3. basePackageClasses:basePackages()类型安全替代方案,用于指定要扫描带注释组件的包。 将扫描指定的每个类的包。(比如A.class位于包p1下面,那么配置了basePackageClasses = A.class后,A所在的包P1以及子包都会被扫描到
    考虑在每个包中创建一个特殊的无操作标记类或接口,除了被此属性引用外,没有其他用途。
    返回:‘basePackageClasses’ 的数组。
  4. clients都是用来指定扫路径:用@FeignClient 注释的类列表。 如果不为空,则禁用类路径扫描。
    返回:FeignClient 类列表
  5. 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 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"));
		}
	}
  1. 获取EnableFeignClients注解所标注的类上改注解的属性
  2. 如果该类有直接外部类Class对象(比如匿名内部类的直接外部类就是该内部类的外部类),则使用直接外部类命名,加上前缀default.
  3. 通过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 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 getbasePackages(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(Map 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 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

 String getName(Map 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 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是怎么创建动态代理的

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/682368.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号