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)