对于不熟悉的人来说,源代码一般是庞大而“杂乱”的,要阅读源代码需要先找到入口点。
而openfeign的入口点在哪呢?当在项目中使用openfeig的时候是通过@EnableFeignClients注解来开启Openfeign,所以这个入口点就是@EnableFeignClients注解:
可以看到其和普通的注解没啥大的不同,除了@Import(FeignClientsRegistrar.class)注解,很显然,@EnableFeignClients注解的核心便是@Import注解。当在某个spring配置类或豆子上标记@EnableFeignClients注解时,相当于通过@import注解同时注册了FeignClientsRegistrar这个豆子----实际上,FeignClientsRegistrar并不是豆子,因为其实现了ImportBeanDefinitionRegistrar这个接口,然后会在registerBeanDefinitions()方法中手动注册豆子来代替FeignClientsRegistrar。总之现在关注点来到了FeignClientsRegistrar。
可以看到, FeignClientsRegistrar类实现了3个接口第一个用来手动注册豆子的,后两个接口是典型的Aware系列接口,可以注入对应的豆子,但这里有个疑问是,FeignClientsRegistrar并不是豆子为何还能注入?而且经过我的试验,这里如果使用ApplicationContextAware接口便不能成功,有待后面研究。于是现在关注点来到了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions()方法
通过方法名能够猜测这里是先注册默认配置,再注册FeignClients,先看看怎么注册默认配置的
这里遇到了AnnotationMetadata类,顾名思义,注解元数据,即被@import注解了的类。这里便是通过该类获取到了被@import注解的类的另外一个注解---@EnableFeignClients的属性(@import注解是放在@EnableFeignClients注解上,然后@EnableFeignClients注解再放到某个配置类上的,所以@import注解便间接地放到了该配置类上)。接下来实际上是设置要注册地豆子地名称,跳过,直接进入registerClientConfiguration方法
没啥说的,通过BeanDefinitionBuilder创建FeignClientSpecification的BeanDefinition,然后直接注册该豆子,名字为default.全类名.FeignClientSpecification,到这里注册默认配置类结束。回到
进入下一个方法
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
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");
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) {
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);
}
}
}
}
这个方法很长,很显然从这里便进入了OpenFeign初始化时的核心代码,即为每个FeignClient注册豆子。要注册豆子就得先找到该类的包路径,于是
先获取@EnableFeignClients注解的所有属性,然后优先检验clients属性的值,若不为空,则只会扫描clients属性指定的类的包路径,而不会去扫描被@feignClient注解了的类了。也就是说若clients设置了feignclient会使@feignClient注解失效!!!
若clients属性的值为空,才会去根据指定的包路径扫描被@feignClient注解了的类。这里的包路径是一个set集合,包含了@EnableFeignClients注解的value、basePackages、basePackagesClasses属性的值,若都为空,则使用被@EnableFeignClients注解的类的同级路径。代码如下:
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;
}
找出包路径后,便开始扫描,这里使用了一个双重循环来扫描每个包路径下的每个类,并进行了一些判断,最终只有被@FeignClient注解了的接口留下来,然后开始注册,要注册就得有名字,所以先决定名字
很显然,名字的优先级从高到低为contextId、value、name、serviceId,若都为空则抛出异常。
找到名字后终于开始注册了
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);
}
显然,注册的豆子并不是被@feignclient注解的类,而是FeignClientFactoryBean,这里涉及到了spring中的FactoryBean,顾名思义就是工厂豆子,这里是hi机上是注册了被@feignclient注解的类的工厂豆子,当要再使用该类的地方注入时,spring会通过对应的工厂豆子的getObject()方法得到要使用的对象。总之factorybean就是一个简单工厂,我们定义好该工厂,要注入时spring会自动从该工厂获取豆子。
这里进行了一些琐碎的操作过后终于结束了openfeign的初始化。
总结一下就是:
当spring初始化时,会经过@EnableFeignClients注解上的@Import(FeignClientsRegistrar.class)注解进入FeignClientsRegistrar类的registerBeanDefinitions方法,之后便会根据注解的属性值找到feign clients们,最后为其注册FeignClientFactoryBean工厂豆子。
接下来就是feignclients的依赖注入……