"No step, no even a thousand miles."
In this article, let's take a look at the core mechanism of feign and how to scan the @ FeignClient annotated interfaces under all packages
The entry is the registerFeignClients(metadata, registry) method
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();
First, get a Scanner here. Pay attention to the name of the Scanner. ClassPathScanningCandidateComponentProvider, as the name suggests, is a Provider that scans candidate components from the classpath. You can simply understand it as a Scanner, and then scans the components in a path under the classpath
clients == null || clients.length == 0
Here is a judgment. This judgment must be true because we did not assign a value to the clients property of the EnableFeignClients annotation
scanner.addIncludeFilter(annotationTypeFilter);
Add an annotation filter to the scanner, with @ FeignClient as the filtering condition. It seems to be used to filter out the interface marked with @ FeignClient annotation under the specified path. You can see it later through the breakpoint
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; }
This method is to parse the final package to be scanned, basePackages
Because in @ EnableFeignClients, we do not configure value, basePackages and basepackagecalasses
So I'll go to basepackages In the logic of isempty()
ClassUtils.getPackageName(importingClassMetadata.getClassName())
Here, the package path where the startup class is located will be obtained through a Utils tool class and returned
After you get the base packages, you start the logic in the for loop
Set candidateComponents = scanner.findCandidateComponents(basePackage);
A key line of code. What does this code do?
The components marked with @ FeignClient annotation are scanned from the basePackages path and placed in a set set
A method of this core must go in and have a look
public Set<BeanDefinition> findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { return scanCandidateComponents(basePackage); } }
If you continue, you will come to the logic of scancandidate components (base package)
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; }
The value of this packageSearchPath is classpath *: COM / WC / resttemplate / * * / * Class, specify the scanning range. Under classpath, start all of the package and its sub packages where the class is located Class file
isCandidateComponent(metadataReader)
Note that the parameter type of this method is MetadataReader!!!
Why should I emphasize this, because an overloaded method with the same name will be called later, but the implementation covered in getScanner() is used!!!
I was a little confused when I saw it. In fact, it didn't matter, because I was a little confused when I saw it for the first time. I didn't understand it until I read it several times later. First remember the conclusion, and then turn it back
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; }
Here, excludeFilters is empty, because we didn't add it, so the above code directly
includeFilters have values. We added an annotation type filter to filter @ FeignClient annotations
Remember that code? scanner.addIncludeFilter(annotationTypeFilter);
Keep going, TF match(metadataReader, getMetadataReaderFactory())
Because my XXXController is currently scanned, naturally there is no @ FeignClient annotation, so false is returned
That is, iscandidatecomponent (metadata reader) returns false, so no operation is performed, and the next cycle is directly performed to scan other classes
Keep going, TF match(metadataReader, getMetadataReaderFactory())
Because this time I scanned the FeignClient annotated class, this match succeeded
The isConditionMatch(metadataReader) method is called and finally returns true. This method does not go in and take a closer look. It belongs to the lower level operation of spring context. It is mainly to see whether the component meets some conditions of condition annotation, such as missingBean. This is the set of automatic configuration. Our business components generally return true
That is, iscandidatecomponent (metadata reader) returns true here
Going down, isCandidateComponent(sbd) enters an overloaded method with the type AnnotatedBeanDefinition
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { AnnotationMetadata metadata = beanDefinition.getMetadata(); return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); }
Do you think you'll get into the above method?
No!
Remember the first line of the registerFeignClients method?
ClassPathScanningCandidateComponentProvider scanner = getScanner();
Let's look at the logic of this 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; } }; }
Think about what I said before, and look at the parameters of this overloaded method, AnnotatedBeanDefinition!!!
You see, the second method actually enters the method overridden in getScanner()
Let's look at the breakpoint to verify it
beanDefinition.getMetadata().isAnnotation()
In this method, we will judge whether the scanned component is an annotation (how to play with annotations ~). If it is not an annotation, we will return a ture. Of course, our feignclient client is not an annotation, but an interface, so we return a ture
candidates.add(sbd);
Then put the @ FeignClient into the set set, and finally return the 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)
The judgment of if means that your filtered bean must be annotated. This must be true, true
Assert.isTrue(annotationMetaCdata.isInterface(), "@FeignClient can only be specified on an interface");
This assertion says that the @ FeignClient annotation must be marked on the interface!!!
Then get the @ FeignClient annotation. The most important attribute we added is to get the name of the service we want to access specified by our @ FeignClient. This name gets the name of the service we want to access
registerClientConfiguration(registry, name, attributes.get("configuration"));
Register the configuration properties of name (service name) and @ FeignClient in BeanDefinitionRegistry. This is not the core thing. Go directly
registerFeignClient(registry, annotationMetadata, attributes);
Finally, register the scanned @ FeignClient interface, which is the key. Let's focus on the analysis in the next article
ok, this article mainly analyzes the core process of feign, creates a scanner, scans out all the interfaces marked with @ FeignClient annotation, and then puts them into a set for subsequent registration. This article may be a little windy, but I must have made it clear. Friends who are not very clear can read it several times. If they don't understand it, they can also leave a message at the bottom of the article. I will answer it in time when I see it.