“不积跬步,无以至千里。”
这篇文章,一起来看看feign的核心机制,看看是如何扫描所有包下的@FeignClient
注解的接口
入口就是这个registerFeignClients(metadata, registry)
方法
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);
}
}
}
}
ClassPathScanningCandidateComponentProvider scanner = getScanner();
首先在这里拿到一个Scanner,扫描器,注意看这个扫描器的名称,ClassPathScanningCandidateComponentProvider,顾名思义,就是从classpath下扫描候选的组件的一个Provider,你可以简单理解为一个扫描器,然后扫描的是classpath下的某个路径里面的组件
clients == null || clients.length == 0
下面有这么一个判断,这个判断一定是true,因为我们并没有给 EnableFeignClients 注解的clients属性赋值
scanner.addIncludeFilter(annotationTypeFilter);
给这个scanner添加一个注解过滤器,以@FeignClient为过滤条件,似乎是用来把指定路径下我们标注@FeignClient注解的接口给过滤出来的,后面可以通过断点来看一下
basePackages = getBasePackages(metadata);
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;
}
这个方法就是在解析最终要扫描的包,basePackages
因为在@EnableFeignClients
中我们value、basePackages和basePackageClasses都没有配置
所以会走到 basePackages.isEmpty() 的逻辑里
ClassUtils.getPackageName(importingClassMetadata.getClassName())
这里会通过一个Utils工具类拿到启动类所在的包路径,并返回
拿到 basePackages 之后,就开始了for循环里的逻辑
Set candidateComponents = scanner.findCandidateComponents(basePackage);
很关键的一行代码,这个代码做了什么事情?
就是从basePackages(启动类所在路径)路径下扫描出来标注了@FeignClient 注解的组件出来,并放在了一个set集合中
这个核心的一个方法肯定要进去看看
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
继续走,会走到 scanCandidateComponents(basePackage) 的逻辑里
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
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);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
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;
}
这个packageSearchPath
的值就是 classpath*:com/wc/resttemplate/**/*.class
,指定扫描范围,classpath下,启动类所在包及其子包的所有.class文件
isCandidateComponent(metadataReader)
这个方法你注意,它的参数类型是 MetadataReader !!!
为什么我要强调这个,因为后面还会调用一个重载的同名方法,但是用的是getScanner() 里面覆盖的实现!!!
看到这里是不是有点疑惑,其实也没什么,因为我第一次看这里也是有点懵,直到后面反复看了几遍才理解,这一块先记住刚才的结论,后面可以翻回来在看看
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
这里excludeFilters
是空,因为我们没有添加,所以上面那段代码直接过
includeFilters
是有值的,我们之前添加过一个annotationTypeFilter
,使用来过滤@FeignClient注解的
还记得那段代码吗? scanner.addIncludeFilter(annotationTypeFilter);
继续走,tf.match(metadataReader, getMetadataReaderFactory())
因为当前扫描的是我的XXXController,自然是没有@FeignClient注解的,所以返回了false
也就是 isCandidateComponent(metadataReader) 返回了false,所以不进行任何操作,直接进行下一个循环,扫描其他类
继续走,tf.match(metadataReader, getMetadataReaderFactory())
因为这一次扫描的是我打了FeignClient注解的类,所以这次match成功了
调用了 isConditionMatch(metadataReader) 方法,最终返回ture,这个方法就不进去细看了,属于spring-context比较底层的操作,主要是看看这个组件是否满足condition注解的一些条件,比如missingBean等,这是自动配置玩儿的那一套,我们业务组件一般返回的都是ture
也就是 isCandidateComponent(metadataReader) 这里返回了ture
往下走, isCandidateComponent(sbd) ,进入了一个重载的方法中,类型是AnnotatedBeanDefinition
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
你以为会进入上面的方法吗?
No!
还记得registerFeignClients方法的第一行代码吗?
ClassPathScanningCandidateComponentProvider scanner = getScanner();
我们看看这个getScanner()
的逻辑
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;
}
};
}
在想想我之前说的,在看看这个重载的方法的参数,AnnotatedBeanDefinition!!!
明白了吧,第二个方法实际上是进入到这个在getScanner()时覆盖的方法里来的
我们看断点,来验证一下
beanDefinition.getMetadata().isAnnotation()
这个方法里,会判断一下,这个扫描的组件是不是一个注解(是注解还怎么玩儿~),如果不是一个注解,就返回一个ture,我们的feignclient客户端当然不是一个注解了,而是一个接口,所以返回了ture
candidates.add(sbd);
然后就把这个@FeignClient放入到set集合中,最后把这个set返回了
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);
}
}
if (candidateComponent instanceof AnnotatedBeanDefinition)
这个if的判断,是说你这个过滤出来的bean,必须是被注解标注的,这个肯定是成立的,ture
Assert.isTrue(annotationMetaCdata.isInterface(), “@FeignClient can only be specified on an interface”);
这个断言都说了,@FeignClient
注解必须标注在接口上!!!
然后拿到@FeignClient注解里,我们配置的属性,主要是获取@FeignClient指定的要访问的服务名称
registerClientConfiguration(registry, name, attributes.get(“configuration”));
注册客户端配置,这里会将name(服务名称)和 @FeignClient的配置属性在 BeanDefinitionRegistry 中进行注册,跟之前的一样,这里不是核心的东西,直接过
registerFeignClient(registry, annotationMetadata, attributes);
最后对扫描出来的@FeignClient接口去进行注册,这里比较关键,后面会着重分析一下
ok,总结一下,registerFeignClients()
方法里核心的东西是一个扫描器,把我们标注了@FeignClient注解的接口全部扫描出来,然后放进一个set,后续再使用 BeanDefinitionRegistry 注册。
这篇文章可能有点绕,但核心的东西肯定是讲解的很清晰了,看完感觉还是有点迷的朋友可以多看几遍,不理解的地方也可以在文章下方留言,我看到也会及时解惑。