Spring – @ ComponentScan analysis

The paper

The ComponentScan annotation specifies the base path for Spring Bean scanning. The specified path loads all beans that conform to the rule (such as the class annotated by the @Component annotation, or ManagedBean, Named). Load them into Spring and their functionality is equivalent to the < Context: Component-scan > tag in the Spring XML configuration file

In addition, in its basic functions, added the resource matching operation, bean name generation, scope annotation parsing, whether to use the default filter. The filter is used to filter which beans can be loaded and which beans do not need to be loaded. This is usually done by loading all beans first and then using the filter for filtering. Find the right resources. The routines in the automatic assembly of Springboot is some, such as the AutoConfigurationImportFilter Springboot, he is mainly used in AutoConfigurationImportSelector. Filters for automatic assembly.

example

Custom annotations @ MyAn annotations, be @ MyAn annotation bean will be added to the Spring, can through the ApplicationContext. GetBean () is available.

Custom annotations

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyAn {
}
Copy the code

The configuration class

@Configuration(proxyBeanMethods = false)
@ComponentScan(includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {MyAn.class})}, useDefaultFilters = true)
public class ScanConfig {}Copy the code

The entity bean

@MyAn
public class TestBeanA
{}@Component
public class TestBeanB {}Copy the code

The main start class

public class TestComponentScan {
	public static void main(String[] args) {
		try {
			AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class);
			TestBeanA bean = context.getBean(TestBeanA.class);
			System.out.println(bean);
			TestBeanB bean2 = context.getBean(TestBeanB.class);
			System.out.println(bean2);
		}catch(Exception e){ e.printStackTrace(); }}}Copy the code

Integral inclusion structure

The purpose of dividing two packets here is to test the specified path of scanning packets later.

The results of

Both Baens can be scanned.

TestBeanA (@myan); TestBeanB (@component); ScanConfig (@configuration); The @componentscan ANNOTATION is annotated, where the filters are specified using the includeFilters attribute, the Bean modified by @myan is the qualified Bean, and the FilterType is specified (filtertype.annotation).

Analysis of the

Analysis is mainly started from the configuration of the class parsing operation, remember the previous Spring – configuration class parsing process, in this simple mention of a mouth @ComponentScan, and did not do a specific analysis, this article will focus on the @ComponentScan content, However, you need to know about configuration class resolution. If you do not know, you are advised to read configuration class resolution first.

Start, directly from the ConfigurationClassParser# doProcessConfigurationClass method

The relevant code is the above, which is divided into two main steps:

  1. Gets the properties of the @ComponentScan annotation on the configuration class.
  2. Loop through, useComponentScanAnnotationParserDo the load operation.

Retrieve attributes have nothing to say, since parse loading (ComponentScanAnnotationParser# parse method), ComponentScanAnnotationParser inside is used to resolve @ ComponentScan annotations.

Inside the parse method using ClassPathBeanDefinitionScanner for scanning. ClassPathBeanDefinitionScanner is to use the Context. The scan, the internal is to use him, but in the Context only provides several methods to configure ClassPathBeanDefinitionScanner several properties, But not the whole story.

In ComponentScanAnnotationParser# parse method is divided into two below:

  1. Create ClassPathBeanDefinitionScanner, from access to @ ComponentScan retrieve attributes in the annotations, configuration ClassPathBeanDefinitionScanner.
  2. To make loading and use ClassPathBeanDefinitionScanner parsing.

Create ClassPathBeanDefinitionScanner, configuration properties

ClassPathBeanDefinitionScanner scanner is a Bean definition information, he can detect a Bean on the classpath, and the corresponding Bean () meet the conditions of Bean registered to a given the BeanFactory, qualified Bean, Beans that meet the criteria are identified by configuring the filter, which by default probes (@Component, @repository, @Service, @Controller). The @ManagedBean and @named annotations are also supported.

create

The constructor

Specify in the constructor whether to use the default filter. The default filter is @Component,@ManagedBean, @named annotations.

Register the default filter with the description of the filter type

The default spring annotation for beans that can be loaded into Spring at scan time is@Component,@ManagedBean,@Name.

As you can see, in the default registration logic, AnnotationTypeFilter is built and added to the includeFilters attribute. I’ll use it later. So let’s look at the logic of the filter

Filter Description

Filters implement the TypeFilter interface, primarily for MetadataReader, to determine which beans are eligible. Can be loaded into Spring.

MetadataReader is used to access the metadata of the class, mainly through ASM access bytecode, to obtain the metadata of the class, in MetadataReader, using the facade design mode. In TypeFilter, you can use the MedataReader to get meta information about the class, such as the class name, interface, parent class, annotation, etc., to match and judge.

Here is not very detailed analysis AbstractTypeHierarchyTraversingFilter and his subclasses of logic. Just in general

First, the matchSelf method is called when a match is made. (This logic is also a bit like the idea of failing fast. If a match is made directly, there is no further operation to be done in order to access bytecode through ASM.)

And then we’re going to matchClassName, the name of the matching method.

After will pass mark position control (AbstractTypeHierarchyTraversingFilter# considerInherited), whether or not to judge the parent class, call matchSuperClass, matching the name of the parent class, if this method returns null, It will recursively go through the matching method, and this time the matching topic is the parent class.

To let us control whether to match the interface by considering the position, let us call matchInterface to match the name of the interface. If this method returns null, we recurse again.

AbstractTypeHierarchyTraversingFilter matching logic basic made myself clear, AssignableTypeFilter and AnnotationTypeFilter is said in the above steps inside through annotations and type to do matching, There’s nothing special about it.


Here, ClassPathBeanDefinitionScanner create have made myself clear, see below his configuration. The relevant code in ComponentScanAnnotationParser# parse (AnnotationAttributes, String), the following

configuration

Subject process: get @ ComponentScan comments inside the properties of the corresponding configuration ClassPathBeanDefinitionScanner.

The code doesn’t have much to say, but I’ll make a few key points here.

IncludeFilters and excludeFilters properties for ComponentScan

First of all, if you know that includeFilters and excludeFilters are both typefilters, but they have different meanings, Response in code is belong to different collection (ClassPathScanningCandidateComponentProvider# includeFilters and excludeFilters).

The annotations provide the filter, which is read, instantiated, and added to the scanner. That’s the main line. Instead, Spring provides several common filters to avoid having to write typeFilters, and their corresponding enumeration values are:

public enum FilterType {

   // Match annotations
    // The corresponding filter is AnnotationTypeFilter
	ANNOTATION,

 // The matching type is AssignableTypeFilter
	ASSIGNABLE_TYPE,

	// Matches the given aspectJ expression
    / / filter is AspectJTypeFilter
	ASPECTJ,

    // Matches the given regular expression
    // Filter corresponds to RegexPatternTypeFilter
	REGEX,

	 / / custom
    // The TypeFilter interface needs to be implemented
	CUSTOM

}
Copy the code

Instantiate the TypeFilter and by different types, by different parameters to build the TypeFilter corresponding code in ComponentScanAnnotationParser# typeFiltersFor. He will have different parameter requirements for different types of filters.

The Filter specified above by ComponentScan is reflected in the code as shown below

Note that includeFilters are an array of @filters.

ComponentScanAnnotationParser also can register a excludeFilters to exclude the configuration class mark @ ComponentScan classes, in simple terms, is to eliminate the configuration class, because the configuration class registration when I was in the first place.

BasePackages and Basepackasses for ComponentScan

About this directly look at the code, can be clear.

The basePackages will get the value first, or if not, the basePackageClasses will get the same package as the basePackageClasses, or if not, the currently declared class. (@ComponentScan) package for annotations. So that explains why the @SpringApplication class in Springboot is written on the outside, because it’s written on the outside, and the path to the scan is the class object that the main boot class is already in, so there’s a problem, You can write Springboot’s main bootstrap class to Springboot, but you have to manually specify the @ComponentScan scan path, otherwise the classes in the main bootstrap package will not be scanned

ComponentScan basePackages and resourcePattern

It’s the suffix for scan. The default is

The complete scan path is as follows

The resourcePattern in the figure is the suffix specified by the @ComponentScan annotation.

Use a ClassPathBeanDefinitionScanner for loading and parsing

After creating and configuring the ClassPathBeanDefinitionScanner configuration above, here to do is to scan, will be a class file loaded into memory, through MetadataReader to access the class files, through the filter for filtering, It is then wrapped and registered with Spring as BeanDefintion.

That’s the general idea. The corresponding code in ClassPathScanningCandidateComponentProvider# scanCandidateComponents, By the way is ClassPathScanningCandidateComponentProvider ClassPathBeanDefinitionScanner parent class.

The code is divided into four parts: (the relevant code in ClassPathBeanDefinitionScanner# doScan)

  1. Scan load class files into memory.

    Determine the path to scan and load the class file into memory (the corresponding Resource object). In this case, load is actually reading the class file and wrapping it with the Resource object

  2. Read the class file information, encapsulated as MetaData.

    Because to get information about a class, such as the name of the class, the annotation of the class, the interface that the class implements, the parent class, and so on, there is a way to get information about the class. In Spring, it’s called MetadataReader.

    AnnotationMetadata and Resource, which correspond to the meta information of the class (interface, parent class, whether final, whether abstract, whether annotation, annotation, annotation, annotation). Interface, etc.), information about annotations in a class, class files.

    In MetadataReader is by creating SimpleAnnotationMetadataReadingVisitor (ASM) to directly access the bytecode class to obtain the information. I don’t know much about bytecode, so I won’t go into it here. Oh, and one more thing, creating MetadataReader is done using the simple Factory design pattern.

  3. Filter by filter.

    After obtaining the MetadataReader, subsequent operations can be accessed through the MetadataReader, which can access the meta information of the class. At this point, the MetadataReader is passed to the TypeFilter to determine which classes can be loaded into Spring. That not line, the corresponding code in ClassPathScanningCandidateComponentProvider# isCandidateComponent. That’s just calling excludeFilters and includeFilters.

  4. Encapsulate it as BeanDefinition and register it with Spring.

    At this point, you have identified beans that you want to register with Spring.

    Create ScannedGenericBeanDefinition, transfer MetadataReader into, thus got the BeanDefintion, after processing by ScopeMetadataResolver @ Scope annotations, production of the Bean name, Processing Bean those Spring annotations (@ Lazy, @ DependsOn etc, the corresponding code in AnnotationConfigUtils# processCommonDefinitionAnnotations), The name of the Bean is generated via BeanNameGenerator, and then a full BeanDefintion is registered with Spring.

    There is also a point here, if the scanned class is a configuration class, it cannot be simply registered, but the configuration class must be resolved.

The general idea is the above said so, will not stick to the specific code, the following around a few problems to talk about in detail.

The problem

  1. How does he scan after he determines the path, prefix and suffix?

  2. How is the Bean name generated and what is the logic behind it? That’s what BeanNameGenerator does.

  3. How does MetaDataReader read a Class file?

Around these a few questions below, spread out say

How does he scan after he determines the path, prefix and suffix?

Specific code in PathMatchingResourcePatternResolver# getResources (String).

This style is called Ant style, and is common in Spring, such as the configuration file location when Spring XML is started. This can be done using the Ant style.

The specific is the following logic to do.

  1. Determine the root path, which is actually the specified scan packet mentioned above.
  2. Determine the matching full path for later judgment
  3. Recursively fetch all files under the following path, passing the file path toPathMatcherTo see if there’s a match.

PathMatcher is used for matching, and one of its important implementation classes is AntPathMatcher,

Match, you can add him to the result, in this way, the result is that all results, can get the class files, and there is not so simple, nature is that, there were a lot of judgment, what if the file system, for example, choose what kind of loader, are in the process of the need to use.

How is the Bean name generated and what is the logic behind it? That’s what BeanNameGenerator does.

Here is the BeanNameGenerator, mainly in ClassPathBeanDefinitionScanner default is AnnotationBeanNameGenerator inside. Below said is around AnnotationBeanNameGenerator# generateBeanName.

The basic idea is as follows:

  1. Since you want to get the bean name from the annotation, you need to get the annotation, get the attribute.
  2. The annotation, however, can’t be mangled, and the property can’t be mangled, or it can’t be retrieved, so we need to add a judgment to determine whether the annotations on the current class are the ones we mentioned before (@component or Component as a meta-annotation, @managedBean, @named). And the specified attribute value value has no.
  3. If there is no such annotation, or if it is not specified in the annotation, a default value is given.

From here, you can do one thing: custom annotations that implement @Component,

Custom annotations that implement @Component functions.

This example is based on the original example

  1. Custom annotations

    First, @Component should be a meta-annotation. The custom property should have a value in it.

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Component
    public @interface MyAn {
    	@AliasFor(annotation = Component.class,value = "value") // Use aliasFor to share attribute values
    	String value(a) default "";
    }
    Copy the code
  2. How to write @ComponentScan on the configuration class?

    You need to specify TypeFIlter, which is of type annotation, and value, which passes custom annotations.

    @Configuration(proxyBeanMethods = false)
    @ComponentScan(includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {MyAn.class})
    }
    )
    public class ScanConfig {}Copy the code
  3. test

    public class TestComponentScan {
    	public static void main(String[] args) {
    		try {
    			AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class);
    			TestBeanA bean = (TestBeanA)context.getBean("testaaa");
    			System.out.println(bean);
    			TestBeanB bean2 = context.getBean(TestBeanB.class);
    			System.out.println(bean2);
    		}catch(Exception e){ e.printStackTrace(); }}}Copy the code
How does MetaDataReader read a Class file?

I can’t understand 😁. As mentioned before, I directly access bytecode through ASM and parse bytecode to obtain information. I have a little knowledge of bytecode.

Specific code inside SimpleMetadataReader constructor, key SimpleAnnotationMetadataReadingVisitor and ClassReader.


Here, @ComponentScan has been resolved, the following combined with the above analysis, their own casual play.

Just a couple of examples

  1. Matches class files in the current directory of the specified package

    1. @ComponentScan How to write a note?

      package compontscan;
      
      @Configuration(proxyBeanMethods = false)
      @ComponentScan(includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {MyAn.class}) }, ResourcePattern = "*. Class "// There is no middle **, there is no basePackage, so use @ComponentScan class directory basePackage, here is ScanConfig. // compontscan/*.class (TestBeanA)
      public class ScanConfig {}Copy the code
    2. Main startup class and organization chart

      public class TestComponentScan {
      	public static void main(String[] args) {
      		try {
      			AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class);
      			TestBeanC bean = (TestBeanC)context.getBean("testc");
      			System.out.println(bean);
      		}catch(Exception e){ e.printStackTrace(); }}}Copy the code

  1. Name of a custom bean.

    Custom annotations that specify a name, but don’t use @Component as a meta-annotation, as mentioned above. The result of customizing NameGenerator is to provide an annotation that specifies the bean name. The following example is based on the above example.

    1. Custom annotations

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      @Documented
      public @interface MyAn {
      	String name(a) default "";
      }
      Copy the code
    2. Custom NameGenerator

      Inheritance AnnotationBeanNameGenerator, rewrite isStereotypeWithNameValue, judging their increased MyAn processing. This is just an example. You can also rewrite BeanNameGenerator#generateBeanName directly.

      public class MyNameGenerator extends AnnotationBeanNameGenerator {
      	private static final String MY_AN = "common.MyAn"; 
      
      	@Override
      	protected boolean isStereotypeWithNameValue(String annotationType, Set<String> metaAnnotationTypes, Map<String, Object> attributes) {
      		boolean isStereotype = super.isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes);
      		return isStereotype ?
      				isStereotype :
      				isMyAnWithNameValue(annotationType, metaAnnotationTypes, attributes);
      	}
      
      	protected boolean isMyAnWithNameValue(String annotationType, Set<String> metaAnnotationTypes, Map<String, Object> attributes) {
      		boolean isStereotype = annotationType.equals(MY_AN) ||
      				metaAnnotationTypes.contains(MY_AN);
      		if((isStereotype && attributes ! =null && attributes.containsKey("name"))) {
      			attributes.put("value",attributes.get("name"));
      			System.out.println(attributes.get("name"));
      			return true;
      		}
      		return false; }}Copy the code
    3. The configuration class adds the @ComponentScan annotation

      Specified by the nameGenerator attribute.

      @Configuration(proxyBeanMethods = false)
      @ComponentScan(includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {MyAn.class}) }, resourcePattern = "*.class", nameGenerator = MyNameGenerator.class )
      public class ScanConfig {}Copy the code
    4. A custom Bean

      @MyAn(name = "testc")
      public class TestBeanC
      {}Copy the code
    5. The main start class

conclusion

@ComponentScan analysis finished, the most straightforward, simple idea is to load the class file in the specified path, use ASM to parse the bytecode, get the meta information of the class, use TypeFilter to determine which classes are available, Parse the conforming class files into BeanDefinitions, which include some commonly used annotations, and then register them with Spring.

However, when specifying a scan package, ant style matching is supported. The general idea is to take all files under the specified scan package and determine whether they match by path.

Also, when deciding which classes to load, you have to choose how to load them, such as file system, classpath, and so on.

As for the blog, I take it as my notes, and there are a lot of contents in it that reflect my thinking process. As my thinking is limited, there are inevitably some discrepancies. If there are any questions, please point them out. Discuss together. thank you