不学无数——Feign源码解析——初始化流程

Feign源码解析——初始化流程

Posted by 不学无数 on November 9, 2018

Feign源码解析——初始化流程

首先我们在使用Feign的时候会加上两个注解,第一个是@EnableFeignClients开启Feign的功能,第二个是在修饰被调用方的接口加上这个注解@FeignClient而生成代理类。那么我们就根据这两个注解为入口进行解析。

@EnableFeignClients注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
		…………
}

@Import注解就是之前xml配置中的import标签,可以用于依赖第三方包中bean的配置和加载

进入@EnableFeignClients注解我们可以发现里面加载了FeignClientsRegistrar类,进去FeignClientsRegistrar 类,我们看到他实现了ImportBeanDefinitionRegistrar这个类,ImportBeanDefinitionRegistrar接口是Spring的一个扩展接口,通过registerBeanDefinitions方法向容器中注入Bean。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    ……………………………省略…………………………………
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}
	………………………………省略……………………………………
}

所以我们可以理解这一步就是将程序中带有@FeignClient 注解的接口注册到Spring容器中。接下来我们深入了解他是如何将什么信息注册进去了

其中registerDefaultConfiguration 方法注册了@EnableFeignClients 注解中defaultConfiguration 属性,重点我们看registerFeignClients 方法。


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());
		//设置要扫描的注解是@FeignClient
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		//查看@EnableFeignClients中设置的clients参数
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		//如果没有设置clients参数,那么设置要扫描的包是@EnableFeignClients中设置的value值
		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)));
		}

		for (String basePackage : basePackages) {
			//扫描设置的包中的@FeignClient注解
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// 验证@ FeignClient注解的类是一个接口
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");
					//获得@ FeignClient所设置的属性
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());
					//获得@FeignClient中设置的value值
					String name = getClientName(attributes);
					//将@FeignClient注解中Configuration属性注册进容器中,其中名字是name的值
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					//下面有讲解
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

registerFeignClient 方法讲解,其实这一块就是将@FeignClient 注解中所有的value值取出并且放入容器中。在这里我们需要注意此时注册是FeignClientFactoryBean类型,之前注册的都是FeignClientSpecification类型的

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);
		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 = name + "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);
	}

到这Feign的初始化流程已经说完了。大概总结一下Feign的初始化流程

  1. 读取@EnableFeignClients注解中basePackage值下的所有带有@FeignClient的接口
  2. 读取接口上面的 @FeignClient 注解参数
  3. 如果此接口上有Configuration参数,那么先进行注册此参数,注意此参数注册在Spring容器中是以FeignClientSpecification类型注册的
  4. 注册完Configuration参数以后,然后将其余的信息注册到容器中,注意这时是以FeignClientFactoryBean 类型注册的,另外此时的Configuration参数并没有传过来。

参考文章