Spring boot2. X basic chapter: take you to know about scanning Package automatic registration Bean

Keywords: Programming Java Spring SpringBoot Attribute

Knowledge changes fate, rolling code makes me happy, and I will continue to swim in the open source industry in 2020 < br / > Take a look at what you like and get into the habit < br / > Give me a Star, Click to learn about the implementation solution of component-based interface service based on spring boot

We have been using SpringBoot to develop applications, but why do we automatically register beans annotated with @ Component, @ Service, @ RestController... When the project starts?

Recommended reading

Default scan directory

SpringBoot takes the Package where the entry class is located as the default scanning directory, which is also a constraint. If we create a class that needs to be registered to IOC in the scanning directory, we can realize automatic registration, otherwise it will not be registered.

If your entry class is called ExampleApplication, it is located in the org.minbox.chapter directory. When we start the application, we will automatically scan all annotated classes under the same level directory and sub level directory of org.minbox.chapter, as shown below:

. src/main/java
├── org.minbox.chapter
│   ├── ExampleApplication.java
│   ├── HelloController.java
│   ├── HelloExample.java
│   └── index
│   │   └── IndexController.java
├── com.hengboy
│   ├── TestController.java
└──

HelloController.java, HelloExample.java are in the same level directory as the entry class ExampleApplication.java, so they can be scanned when the project is started.

IndexController.java is located in the lower directory org.minbox.chapter.index of the entry class. Because it supports lower directory scanning, it can also be scanned.

TestController.java is located in the com.hengboy directory and cannot be scanned by default.

Custom scan directory

In the above directory structure, the TestController.java class in the com.hengboy directory cannot be scanned and registered in the IOC container by default. If you want to scan the classes in this directory, there are two methods.

Method 1: use @ ComponentScan annotation

@ComponentScan({"org.minbox.chapter", "com.hengboy"})

Method 2: use the scanBasePackages property

@SpringBootApplication(scanBasePackages = {"org.minbox.chapter", "com.hengboy"})

Note: after the custom scan directory is configured, the Default scan directory will be overwritten. If you also need to scan the default directory, you need to configure the scan directory. In the above custom configuration, if only the scan com.hengboy directory is configured, the org.minbox.chapter directory will not be scanned.

Tracing source code

Now let's see how the spring boot source code can automatically scan the beans in the directory and register the beans in the container.

Due to the complexity of the registration process, representative process steps are selected to explain.

Get BasePackages

In the org.springframework.context.annotation.componentscananannotationparser method, there is a business logic to obtain basePackages. The source code is as follows:

Set<String> basePackages = new LinkedHashSet<>();
// Get the basePackages property value of @ ComponentScan annotation configuration
String[] basePackagesArray = componentScan.getStringArray("basePackages");
// Add the basePackages property value to the Set collection
for (String pkg : basePackagesArray) {
  String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                                                         ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  Collections.addAll(basePackages, tokenized);
}
// Get the basePackageClasses property value of @ ComponentScan annotation
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
  // Get the package of basePackageClasses and add it to the Set set
  basePackages.add(ClassUtils.getPackageName(clazz));
}
// If the value of basePackages and basePackageClasses property of @ ComponentScan is not configured
if (basePackages.isEmpty()) {
  // Use the package of Application entry class as basePackage
  basePackages.add(ClassUtils.getPackageName(declaringClass));
}

There are three steps to obtain basePackages, namely:

  1. Get the value of the @ ComponentScan annotation basePackages property
  2. Get @ ComponentScan annotation basePackageClasses property value
  3. Use the package of the Application entry class as the default basePackages

Note: according to the source code, it is also confirmed why we will overwrite the default value after configuring basePackages and basePackageClasses. In fact, this is not an overwrite, and we will not get the package of the Application entry class at all.

Scan beans under Packages

After obtaining all Packages, scan the classes marked with registration annotation (@ Component, @ Service, @ RestController...) under each Package through org.springframework.context.annotation.classpathbeandefinitionscanner 3535; doscan method. The source code is as follows:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  // Throw IllegalArgumentException exception when basePackages is empty
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  // Traverse each basePackage and scan all beans under the package
  for (String basePackage : basePackages) {
    // Get all the beans scanned
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    // Traverse each Bean to handle registration related matters
    for (BeanDefinition candidate : candidates) {
      // Get metadata for scope
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
      candidate.setScope(scopeMetadata.getScopeName());
      // Get the Name of the Bean
      String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
      if (candidate instanceof AbstractBeanDefinition) {
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      }
      // If it is a Bean registered in annotation mode
      if (candidate instanceof AnnotatedBeanDefinition) {
        // Handle the annotation attribute on the Bean, and set it to the field in the Bean definition (AnnotatedBeanDefinition) class
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      }
      // Check whether the registration conditions are met
      if (checkCandidate(beanName, candidate)) {
        // Declare the basic properties of a Bean
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
        // Apply scope agent mode
        definitionHolder =
          AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        // Write returned collection
        beanDefinitions.add(definitionHolder);
        // Registered Bean
        registerBeanDefinition(definitionHolder, this.registry);
      }
    }
  }
  return beanDefinitions;
}

In the above source code, we will scan the Bean defined by annotation under each basePackage, obtain the Bean registration definition object and set some basic properties.

Registered Bean

After scanning the Bean under basePackage, it will be registered directly through org.springframework.beans.factory.support.beandefinitionreaderutils ා registerbeandefinition method. The source code is as follows:

public static void registerBeanDefinition(
  BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
  throws BeanDefinitionStoreException {

  // Unique name of the registered Bean
  String beanName = definitionHolder.getBeanName();
  // Register Bean through BeanDefinitionRegistry registrar
  registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

  // If there is an alias, register the alias of the Bean
  String[] aliases = definitionHolder.getAliases();
  if (aliases != null) {
    for (String alias : aliases) {
      registry.registerAlias(beanName, alias);
    }
  }
}

Through the method in org.springframework.beans.factory.support.beandefinitionregistry ා registerbeandefinition registrar, you can directly register a Bean into the IOC container, and BeanName is the only name in its life cycle.

summary

Through the explanation of this article, I think you should have understood why spring boot application automatically scans package and registers Bean into IOC container when it starts. Although the project start-up time is short, it is a very complex process. In the learning process, you can use Debug mode to view the logic processing of each step.

Author individual Blog Using open source framework ApiBoot Help you become Api interface service architect

Posted by lazytiger on Tue, 25 Feb 2020 00:54:47 -0800