Feign源码之@EnableFeignClients

17 篇文章 2 订阅
15 篇文章 1 订阅

注解属性

先看一下@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 {};
}
  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<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"));
		}
	}
  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<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是怎么创建动态代理的

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
@FeignClient和@EnableFeignClients都与使用Feign框架创建和使用远程调用客户端有关。 @FeignClient是一个注解,用于标识一个接口作为Feign客户端。在这个注解中,我们可以指定远程服务的名称、URL、负载均衡策略等信息。通过@FeignClient注解,我们可以将一个接口定义为一个Feign客户端。 @EnableFeignClients也是一个注解,用于启用Feign客户端的自动配置。当我们在Spring Boot应用中使用Feign框架时,通过在主配置类上添加@EnableFeignClients注解,可以告诉框架扫描所有使用@FeignClient注解定义的Feign客户端,并自动为其生成代理对象。 FeignClientsRegistrar是@EnableFeignClients注解中通过@Import导入的类,它实现了BeanDefinitionRegistryPostProcessor接口,用于扫描和注册使用@FeignClient注解定义的Feign客户端的bean定义。它会解析@FeignClient注解中的配置信息,并根据配置创建Feign客户端的代理对象。 总结起来,@FeignClient用于标识一个接口作为Feign客户端,@EnableFeignClients用于启用Feign客户端的自动配置,而FeignClientsRegistrar则是@EnableFeignClients注解的实现,用于扫描和注册使用@FeignClient注解定义的Feign客户端的bean定义。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [使用feign客户端要使用注解@EnableFeignClients](https://blog.csdn.net/xingxiupaioxue/article/details/105124391)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [彻底搞懂Feign——EnableFeignClient底层机制探究](https://blog.csdn.net/qq_45455361/article/details/121459795)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值