SpringBoot's core annotations and automatic configuration principles


1 Introduction

        SpringBoot's automatic configuration principle is a headache. After an afternoon of study, I wrote this article for your reference. If there is any similarity, it is purely coincidental. If there is an error in the article, you are welcome to point it out, and I will modify it at any time.
        SpringBoot version: 2.7.5 (note the version number, otherwise it may be inconsistent with the debugging results in the article).

2. Core annotations of SpringBoot

@SpringBootApplication
public class DemoApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DemoApplication.class, args);
    }
}

        When we create a springboot project, we will write a main startup class. Every time we start the project, we only need to run the main method in the main startup class. The @SpringBootApplication annotation marked on the main startup class is the core annotation of springboot, but this annotation is a synthetic annotation, mainly composed of the following three annotations:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {
    
    @Filter(
    type = FilterType.CUSTOM,
    classes = {
    
    TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {
    
    AutoConfigurationExcludeFilter.class}
)}
)

The @SpringBootConfiguration
        @SpringBootConfiguration annotation is used to mark the class as a configuration class. It has the same function as the @Configuration annotation, the difference is that @SpringBootConfiguration is an annotation in springboot, while @Configuration annotation is an annotation in spring. It can be seen from the following code that @SpringBootConfiguration is a derived annotation of @Configuration.

@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    
    
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@ComponentScan
        The function of the @ComponentScan annotation is to scan all the classes in the specified package and its subpackages, and register the classes full of filter conditions in the spring container as beans. By default, all classes in the package where the main startup class is located and its subpackages are scanned.
@EnableAutoConfiguration
        The function of the @EnableAutoConfiguration annotation is to enable the automatic configuration function. With this annotation, the program will load various AutoConfiguration classes registered in the META-INF/spring.factories file. When an AutoConfiguration class meets the specified effective conditions, the bean defined in the AutoConfiguration class will be instantiated and finally injected into the Spring container In this way, the automatic configuration of the dependent framework is completed. The following automatic configuration explanations are all carried out through this annotation.

3. Automatic configuration of SpringBoot

@EnableAutoConfiguration:

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({
    
    AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    
    
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {
    
    };

    String[] excludeName() default {
    
    };
}

        The @EnableAutoConfiguration annotation is mainly composed of @AutoConfigurationPackage and @Import.

3.1 @AutoConfigurationPackage annotation

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    
    
}

Registrar class:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
    
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    
    
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
    
    
			return Collections.singleton(new PackageImports(metadata));
		}
	}

        The @AutoConfigurationPackage annotation uses the @Import annotation at the bottom to import Registrar into the container. The role of the Registrar class is to register the automatic configuration package name in the form of programming to record the entry of the package scan. Which package name is obtained?
insert image description here
insert image description here
        Let's look at the metadata parameter in the registerBeanDefinitions method (this parameter represents the annotation metadata), which includes information such as the location of the annotation, and the location of the annotation is on the startup class, so the obtained package name is the package where the startup class is located. Looking at the figure above, the result calculated by the code is the package where the main startup class is located.
        Many people have questions here. The annotation on the startup class is @SpringbootApplication. Why can I get the package name? In fact, the annotation position in the metadata metadata refers to the @AutoConfigurationPackage position, and @SpringbootApplication is a synthetic annotation, which includes the @EnableAutoConfiguration annotation, and the @EnableAutoConfiguration annotation includes the @AutoConfigurationPackage annotation, so the package name in the metadata is the main startup class The package name.

3.2 @Import annotation

        Let's look at the second important annotation @Import in the @EnableAutoConfiguration annotation. Its function is to import the AutoConfigurationImportSelector class into the container as a component. Next, let's analyze the AutoConfigurationImportSelector class.

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    
    
		if (!isEnabled(annotationMetadata)) {
    
    
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

        Which components are in the array returned by the selectImports method, then the program will import which components. Component information is acquired through the getAutoConfigurationEntry method.

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    
    
		if (!isEnabled(annotationMetadata)) {
    
    
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

        By analyzing the getAutoConfigurationEntry method, we can see that this method still needs to call the getCandidateConfigurations method to obtain data, and then return to the selectImports method after a series of operations such as removing duplicate components. Next, we continue to analyze the getCandidateConfigurations method.

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
    
		List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    
    
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
    
    
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
  private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    
    
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
    
    
            return result;
        } else {
    
    
            Map<String, List<String>> result = new HashMap();
            try {
    
    
                Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

                while(urls.hasMoreElements()) {
    
    
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
    
    
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
    
    
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
    
    
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

        In order not to talk nonsense, just paste all the codes and analyze them together. The overall logic is that the getCandidateConfigurations method calls the loadFactoryNames method, and then the loadFactoryNames method calls the loadSpringFactories method. We can directly analyze loadSpringFactories.
        We can know from line 8 of the loadSpringFactories method that the acquisition of components is obtained by scanning spring.factories under the META-INF folder.
Result:
spring.factories file:
insert image description here
Number of configurations:
insert image description here
        Hahaha, did you find a new problem. . . You are right, far from 144 in the spring.factories file. After my efforts, I finally found the problem. In the springboot2.7.5 version, the automatic configuration that needs to be loaded has been placed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports instead of being written in the spring.factories file.
Official website original text:
insert image description here
insert image description here
        There are exactly 144 configuration classes that need to be loaded in this file.

4. Turn on automatic configuration as needed

        The above explains how springboot loads all automatic configuration classes, but in fact, not all automatic configuration classes are loaded, but are loaded on demand. How to load on demand, we give two examples.

4.1 Take AopAutoConfiguration as an example

@AutoConfiguration
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
    
    
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration {
    
    
		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
		static class JdkDynamicAutoProxyConfiguration {
    
    
		}
		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
				matchIfMissing = true)
		static class CglibAutoProxyConfiguration {
    
    
		}
	}
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	static class ClassProxyingConfiguration {
    
    

		@Bean
		static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
    
    
			return (beanFactory) -> {
    
    
				if (beanFactory instanceof BeanDefinitionRegistry) {
    
    
					BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
				}
			};
		}

	}
}

        The @ConditionalOnProperty annotation is added to the AopAutoConfiguration class, indicating that if spring.aop.auto=true exists in the configuration file, then the configuration class will be loaded, and matchIfMissing = true means that the configuration class will also take effect even if the configuration file is not configured.
        The 5th line of code above (@ConditionalOnClass(Advice.class)) means that if the Advice class exists, the AspectJAutoProxyingConfiguration configuration class will take effect; the 20th line of code above (@ConditionalOnMissingClass("org.aspectj.weaver.Advice")) means Advice If the class does not exist, the ClassProxyingConfiguration configuration class takes effect.
        It can be seen that when the jar package related to aspectj is not imported, AOP in springboot uses JDK dynamic proxy to implement by default.

4.2 Take BatchAutoConfiguration as an example

@AutoConfiguration(after = HibernateJpaAutoConfiguration.class)
@ConditionalOnClass({
    
     JobLauncher.class, DataSource.class })
@ConditionalOnBean(JobLauncher.class)
@EnableConfigurationProperties(BatchProperties.class)
@Import({
    
     BatchConfigurerConfiguration.class, DatabaseInitializationDependencyConfigurer.class })
public class BatchAutoConfiguration {
    
    
}

        As in the above code (the second and third lines of code), the configuration class will only take effect when the JobLauncher class exists and the bean corresponding to the JobLauncher exists in the container, otherwise the configuration will not be enabled.
        Summary: In springboot, all configuration classes are loaded by default and on-demand automatic configuration classes are enabled. The automatic configuration function is realized through a series of annotations such as @ConditionalOnClass, @ConditionalOnProperty and @ConditionalOnMissingClass.

5. Summary

  • The core annotation in SpringBoot is @SpringBootApplication, but this annotation is a composite annotation, mainly composed of three annotations: @SpringBootConfiguration, @EnableAutoConfiguration and @ComponentScan.
  • The automatic configuration of SpringBoot is realized by scanning the spring.factories file under the META-INF folder in spring-boot-configuration, and then loading the corresponding configuration class.
  • SpringBoot's automatic configuration does not load all configuration classes, but configures on demand, mainly through a series of annotations such as @ConditionalOnClass, @ConditionalOnProperty and @ConditionalOnMissingClass.

Note: In previous versions, SpringBoot would scan the spring.factories file under the META-INF folder, which was changed to org.springframework.boot under the spring folder in SpringBoot2.7.5 (do not know from which version). autoconfigure.AutoConfiguration.imports file.

Reference: [Silicon Valley] SpringBoot2 Zero-Basic Introductory Tutorial

Guess you like

Origin blog.csdn.net/m0_73845616/article/details/127948559