Feign源码的入口
首先看一下demo工具的整体结构,ServiceB和ServiceA都要注册到eureka中,而serviceA-api是一个通用的api模块,由于Feign具有继承的特性,所以把接口都抽取出来了。
具体的代码如下:
@RequestMapping("/user")
public interface ServiceAInterface {
@RequestMapping(value = "/sayHello/{id}", method = RequestMethod.GET)
String sayHello(@PathVariable("id") Long id,
@RequestParam("name") String name,
@RequestParam("age") Integer age);
@RequestMapping(value = "/", method = RequestMethod.POST)
String createUser(@RequestBody User user);
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
String updateUser(@PathVariable("id") Long id, @RequestBody User user);
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
String deleteUser(@PathVariable("id") Long id);
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
User getById(@PathVariable("id") Long id);
}
而在ServiceAClient中,只要对这个接口进行继承,加上注解就可以了。
@FeignClient("ServiceA")
public interface ServiceAClient extends ServiceAInterface {
}
还要在启动上加上开启Feign客户端的注解@EnableFeignClients,就能起作用了。那我们就点进去这个注解看看。
@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 {};
}
注解上面会注入Feign客户端的注册器,那我们就点进去看看。
importBeanDefinitionRegistrar来实现Bean的动态注入。它是Spring中一个强大的扩展接口,可以动态的往容器中,注入Bean,基本可以确定,就是实现它,来往容器中注入Feign的客户端的。
@Override
public void registerBeanDefinitions(Annotationmetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
首先先看一下第一个方法,名字注册一些默认的配置信息,点进去看看。大概的意思就是获取到EnableFeignClients这个注解上的一些信息,如果信息不为空,且包含模型配置的leu,就进入下面的判断。此方法不是核心的方法,主要留意一下registerFeignClients方法。
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"));
}
}
那就点到registerFeignClients方法看看。
public void registerFeignClients(Annotationmetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set basePackages;
//拿到注解的一些信息
Map attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//设置注解类型的过滤器,看上去是为了获取标记FeignClient这个注解类
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class>[] clients = attrs == null ? null
: (Class>[]) attrs.get("clients");
//由于@EnableFeignClients注解中来配置clients属性,一般不会填,所以可以clients默认一般都是空的
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
//getbasePackages这里肯定获取@EnableFeignClients这个注解的包的路径
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)));
}
for (String basePackage : basePackages) {
//1.这步的话就是根据包路径,去找到这个包下面有哪些FeignClient注解的类
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);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationmetadata, attributes);
}
}
}
}
下面这个方法至关重要,那我们就点进去看看
SetcandidateComponents = scanner .findCandidateComponents(basePackage);
下面就来到scanCandidateComponents方法中
private SetscanCandidateComponents(String basePackage) { Set candidates = new linkedHashSet<>(); try { //此处就是包的路径加上*.class String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolvebasePackage(basePackage) + '/' + this.resourcePattern; //根据上面得到处理的路径,去拿到包路径下面所有的class文件,转成一个个Resource Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); //遍历所有的资源 for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { metadataReader metadataReader = getmetadataReaderFactory().getmetadataReader(resource); //此处方法最为重要,由于前面创建的scanner。重写了isCandidateComponent方法,这里就是从所有的class中 // 获取所有有的Feign的客户端 if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { logger.trace("Ignored because not matching any filter: " + resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } else { if (traceEnabled) { logger.trace("Ignored because not readable: " + resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return candidates; }
下面就断点进去看一下那个方法,就是之前把过滤条件传进来了,此处就可以获得了。
拿到所有的Feign客户端就准备开始注册了



