In the previous article Spring IoC source code analysis (based on annotations) to our analysis, we start by AnnotationConfigApplicationContext class incoming packet path after Spring, will first initialization packet scanning filtering rules. So let’s look at the specific process of packet scanning today.
Or look at the following code: AnnotationConfigApplicationContext class
// The constructor automatically scans all classes under the given package and its subpackages, and automatically identifies all Spring beans and registers them with the container
public AnnotationConfigApplicationContext(String... basePackages) {
/ / initialization
this(a);// Scan the package and register the bean
scan(basePackages);
refresh();
}
Copy the code
We analyzed this above () method, to initialize AnnotatedBeanDefinitionReader reader and ClassPathBeanDefinitionScanner scanner, scanning and initialize the filtering rules. Let’s have a look at the scan (basePackages) methods: have been tracking down, found that invokes the ClassPathBeanDefinitionScanner scan in class () method
// Call the classpath Bean to define the scanner entry method
public int scan(String... basePackages) {
// Get the number of registered beans in the container
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// Start the scanner to scan the given packet
doScan(basePackages);
// Register annotation config processors, if necessary.
// Register the Annotation Config handler
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// Return the number of registered beans
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
Copy the code
We can see that the main method is doScan(basePackages) to implement the logic of scanning, let’s continue to trace into the look
// The classpath Bean defines the scanner to scan the given package and its subpackages
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
// Create a collection of encapsulated classes scanned to Bean definitions
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// Traversal scans all given packets
for (String basePackage : basePackages) {
/ / call the superclass ClassPathScanningCandidateComponentProvider method
// Scan the given classpath to get the Bean definitions that match the criteria
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// Iterate over the scanned beans
for (BeanDefinition candidate : candidates) {
// Get the value of the @scope annotation, which gets the Scope of the Bean
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
// Set the scope for the Bean
candidate.setScope(scopeMetadata.getScopeName());
// Generate a name for the Bean
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// If the scanned Bean is not a Spring annotated Bean, set the default value for the Bean,
// Set the Bean's auto dependency injection assembly properties, etc
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// If the scanned Bean is a Spring annotated Bean, its general Spring annotations are processed
if (candidate instanceof AnnotatedBeanDefinition) {
// Handle common annotations in annotation beans, which were analyzed when analyzing annotation beans defining class readers
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// Check whether the specified Bean needs to be registered in the container based on the Bean name, or if there is a conflict in the container
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// Apply the appropriate proxy mode to the Bean according to the scope configured in the annotation
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// Register scanned beans with the container
registerBeanDefinition(definitionHolder, this.registry); }}}return beanDefinitions;
}
Copy the code
This large piece of code is basically code that Spring scans to recognize annotations and registers beans into the IOC container. In line 10 there is a findCandidateComponents(basePackage) method, which contains the specific scan logic. Continue to follow: ClassPathScanningCandidateComponentProvider class
// Scan the package for the given classpath
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// If spring5.0 starts indexing, this generated file is loaded directly from local files.
if (this.componentsIndex ! =null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
returnscanCandidateComponents(basePackage); }}Copy the code
There’s an if judgment, and we default to the else branch, the scanCandidateComponents(basePackage) method.
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// Complete the scan path and scan all. Class files classpath*:com/mydemo/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// Locate resources
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 {
// fetch class metadata through ASM and encapsulate it in MetadataReader MetadataReader
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// Check whether the class complies with @compoentScan filtering rules
// Filter matching excludes excludeFilters (may not exist), including the inclusion filter in the includeFilter (contains at least one).
if (isCandidateComponent(metadataReader)) {
// Convert metadata to BeanDefinition
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
// Determine if it is a qualified bean definition
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
// add to the collection
candidates.add(sbd);
}
else {
// Failure is not a top-level, concrete class
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: "+ resource); }}}else {
// Does not comply with @compoentScan filtering rules
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;
}
Copy the code
Here is the main scanning logic, which is made clear by the comments in the code. Main process:
-
Scan all. Class files based on the package path
-
According to the package path, generate the Resource object corresponding to. Class
-
Class metadata is obtained through ASM and encapsulated in the MetadataReader MetadataReader
-
Check whether the class complies with filtering rules
-
Determine whether the class is a separate, concrete class
-
Add to the set
Let’s take a look at the filtering method in detail.
// Determine whether the classes read by the meta-information reader conform to the annotation filtering rules defined by the container
// @compoentScan supports 5 filtering rules (annotation, class, regular, AOP, custom)
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
// Return false if the class's annotation is in the exclude annotation filter
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false; }}// Return true if the annotation of the class read is in the filter rule for contained annotations
for (TypeFilter tf : this.includeFilters) {
// Determine whether the annotations of the current class are match rules
if (tf.match(metadataReader, getMetadataReaderFactory())) {
// If there is an @Conditional annotation, do the relevant processing
returnisConditionMatch(metadataReader); }}// Return false if the class's annotation is not in an exclusion or inclusion rule
return false;
}
Copy the code
Then trace tf. The match () method AbstractTypeHierarchyTraversingFilter class
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// This method optimizes avoiding unnecessary creation of ClassReaders
// as well as visiting over those readers.
// Check whether the annotations of the current class conform to the rule
if (matchSelf(metadataReader)) {
return true;
}
//check Whether the class name complies with the rule
ClassMetadata metadata = metadataReader.getClassMetadata();
if (matchClassName(metadata.getClassName())) {
return true;
}
// If there is an inherited parent class
if (this.considerInherited) {
String superClassName = metadata.getSuperClassName();
if(superClassName ! =null) {
// Optimization to avoid creating ClassReader for super class.
Boolean superClassMatch = matchSuperClass(superClassName);
if(superClassMatch ! =null) {
if (superClassMatch.booleanValue()) {
return true; }}else {
// Need to read super class to determine a match...
try {
if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
return true; }}catch (IOException ex) {
logger.debug("Could not read super class [" + metadata.getSuperClassName() +
"] of type-filtered class [" + metadata.getClassName() + "]"); }}}}// If there is an implementation interface
if (this.considerInterfaces) {
for (String ifc : metadata.getInterfaceNames()) {
// Optimization to avoid creating ClassReader for super class
Boolean interfaceMatch = matchInterface(ifc);
if(interfaceMatch ! =null) {
if (interfaceMatch.booleanValue()) {
return true; }}else {
// Need to read interface to determine a match...
try {
if (match(ifc, metadataReaderFactory)) {
return true; }}catch (IOException ex) {
logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +
metadata.getClassName() + "]"); }}}}return false;
}
Copy the code
The main one is the matchSelf(metadataReader) method AnnotationTypeFilter class
protected boolean matchSelf(MetadataReader metadataReader) {
// Get annotation metadata
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
// Whether the check annotation and its derived annotations contain @Component
// Get the current class's annotation metadata.hasannotation@controller
HasAnnotation @Controller has @Component\ @documented and so on
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
Copy the code
How does Spring find @Configuration, @Controller, and @Service classes? The @Configuration, @Controller, and @Service annotations are derived from @Component annotations. If we look at the code for these annotations, we will see that they are decorated with @Component annotations. And the spring through the metadata. HasMetaAnnotation () method to get to these annotations contain @ Component, so can scan. As follows:
Then we look back at the scanCandidateComponents(basePackage) method, and then we have an isCandidateComponent(SBD) method, as follows:
// Whether it is an independent class, concrete class
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
Copy the code
And what this method does is, it determines whether that class is
-
Top-level class (no parent or static inner class)
-
Concrete classes (not abstract classes or interfaces)
At this point, ClassPathBeanDefinitionScanner class doScan (basePackages) method of findCandidateComponents (basePackage) method has been and gone, that our package scanning is finished, Now that the scanned classes are stored in the collection, it’s time to parse the registered beans.
conclusion
With this article, we can answer some of the previous questions:
-
How does Spring find classes modified by @Controller and @Service annotations? Use the matchSelf(metadataReader) method to determine whether these annotations contain @Component
-
How does the @compoentScan annotation work? Filter by isCandidateComponent(metadataReader)