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:
- Get the value of the @ ComponentScan annotation basePackages property
- Get @ ComponentScan annotation basePackageClasses property value
- 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.