Automatic configuration of source code analysis

Posted by abigbluewhale on Sat, 29 Jan 2022 18:00:54 +0100

automatic assembly

According to the jar package dependency we added, some configuration class bean s will be automatically registered into the ioc container. We can use @ autowired or @ resource annotations where necessary

Question: how is Spring Boot automatically configured and which components are automatically configured?

We know that the startup entry of Spring Boot application is the main() method in the @ SpringBootApplication annotation class.

@SpringBoot application: the SpringBoot application label on a class indicates that this class is the main configuration class of SpringBoot. SpringBoot should run the main() method of this class to start the SpringBoot application.

@What is the SpringBootApplication annotation? What's the usage?

Next, check the internal source code of @ SpringBootApplication for analysis. The core code is as follows

@Target(ElementType.TYPE) //The scope of application of annotations. Type indicates that annotations can be described in classes, interfaces, annotations or enumerations
@Retention(RetentionPolicy.RUNTIME) //Represents the lifecycle of the annotation, Runtime
@Documented //Indicates that annotations can be recorded in javadoc
@Inherited //Indicates that the annotation can be inherited by subclasses
//------------------------------------------------
@SpringBootConfiguration  // Indicates that this class is a configuration class
@EnableAutoConfiguration  // Start automatic configuration function
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) //Annotation scanning
public @interface SpringBootApplication {


	// Exclude specific classes according to class so that they cannot be added to the spring container. The value type of the passed in parameter is class type.
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};


	// Exclude a specific class according to the classname so that it cannot be added to the spring container. The passed in parameter value type is the full class name string array of class
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};


	// Specifies the scanning package. The parameter is a string array of package names.
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	// Scan specific packages, and the parameters are similar to Class type arrays.
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

As can be seen from the above source code, @ SpringBootApplication annotation is a composite annotation. The first four are the metadata information of the annotation. We mainly look at the following three annotations: @ SpringBootConfiguration, @ EnableAutoConfiguration and @ ComponentScan. The relevant descriptions of these three core annotations are as follows

@SpringBootConfiguration

@SpringBootConfiguration: the configuration class of SpringBoot, which is marked on a class to indicate that it is a SpringBoot configuration class. The core code is as follows.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // The function of the configuration class is the same as that of the configuration file. The configuration class is also an object in the container
public @interface SpringBootConfiguration {
}

 

As can be seen from the above source code, there is a core annotation @ Configuration inside the @ SpringBootConfiguration annotation, which is provided by the Spring framework, indicating that the current class is a Configuration class (annotation representation of XML Configuration file) and can be scanned by the component scanner. It can be seen that the @ SpringBootConfiguration annotation has the same function as the @ Configuration annotation. It identifies a Configuration class that can be scanned by the component scanner, but @ SpringBootConfiguration is repackaged and named by Spring Boot

@EnableAutoConfiguration

The source code is as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage // Auto configuration package
@Import(AutoConfigurationImportSelector.class) // Spring's bottom annotation @ Import imports a component into the container;
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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


	String[] excludeName() default {};

}

Spring has many annotations beginning with Enable. Its function is to collect and register beans related to specific scenes with the help of @ Import and load them into the IOC container.

@AutoConfigurationPackage

	// Spring's bottom annotation @ Import imports a component into the container;
	// The imported component is autoconfigurationpackages Registrar. class
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

@Import(AutoConfigurationPackages.Registrar.class), which imports the component class of registrar into the container. You can view the registerBeanDefinitions method in the Registrar class:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			// Pass in the meta information marked by the annotation to obtain the corresponding package name
			register(registry, new PackageImport(metadata).getPackageName());
		}

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

	}

We are interested in new packageimport (metadata) Getpackagename() to search and see what the result is? Click in method to see what has been implemented

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		// The parameter packageNames here is a string by default, and annotations are used
		// @The package of the Spring Boot application entry class of the SpringBootApplication

		if (registry.containsBeanDefinition(BEAN)) {
			// If the bean is already registered, add the package name to be registered
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
		else {
			//If the bean has not been registered, the bean will be registered, and the package name provided in the parameter will be set to the bean definition
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

AutoConfigurationPackages. The class registrar does one thing: register a Bean, which is org springframework. boot. autoconfigure. AutoConfigurationPackages. Basepackages, which has a parameter. This parameter is the package path where the class annotated with @ AutoConfigurationPackage is located. The automatic configuration class is saved for later use. This is why springboot by default scans less the package of the current class and its sub packages, and parses the classes that need to be managed by ioc and puts them into the springioc container.

@Import(AutoConfigurationImportSelector.class)

@Import(AutoConfigurationImportSelector.class): import the AutoConfigurationImportSelector class into the Spring container. AutoConfigurationImportSelector can help springboot applications load all qualified @ Configuration configurations into the IOC container (ApplicationContext) created and used by the current springboot.

You can see that AutoConfigurationImportSelector focuses on the implementation of DeferredImportSelector interface and various Aware interfaces, and then DeferredImportSelector interface inherits ImportSelector interface. It not only implements the ImportSelector interface, but also implements many other Aware interfaces, which respectively indicate that the opportunity is called back at a certain time.

The entry method related to automatic configuration logic is at the getImports method of DeferredImportSelectorGrouping class. Therefore, we will start from the getImports method of DeferredImportSelectorGrouping class to analyze the automatic configuration source code of SpringBoot

public Iterable<Group.Entry> getImports() {
// Traverse the DeferredImportSelectorHolder object set deferredImports. The deferredImports set contains various importselectors. Of course, AutoConfigurationImportSelector is installed here
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
// [1] , use the process method of AutoConfigurationGroup to process the logic related to automatic configuration and decide which configuration classes to import (this is the focus of our analysis, and the logic of automatic configuration is all here)
this.group.process(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getImportSelector());
}
// [2] , after the above processing, select which configuration classes to import
return this.group.selectImports();
}

The code at [1] is the top priority of our analysis. Most of the logic related to automatic configuration is here. So this group. process(deferredImport.getConfigurationClass(). getMetadata(), deferredImport. getImportSelector()) ; The main thing to do is in this Group is the process method of AutoConfigurationGroup object. The incoming AutoConfigurationImportSelector object selects some qualified automatic configuration classes and filters out some unqualified automatic configuration classes.

Enter the pross method of AutoConfigurationImportSelector$AutoConfigurationGroup:

	// This is used to process automatic configuration classes, such as filtering out automatic configuration classes that do not meet matching conditions
		@Override
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));

			// [1] , call the getAutoConfigurationEntry method to get the autoconfiguration class and put it into the autoConfigurationEntry object
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);

			// [2] , then put the autoConfigurationEntry object that encapsulates the autoconfigurationclass into the autoConfigurationEntries collection
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			// [3] , traverse the auto configuration class just obtained
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				// Here, the qualified automatic configuration class is put into the entries collection as the key and the annotationMetadata as the value
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

In the above code, let's take another look at the method getAutoConfigurationEntry marked [1]. This method is mainly used to obtain the automatic configuration class and undertakes the main logic of automatic configuration. Direct code:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		// Get whether spring is configured boot. The enableautoconfiguration property returns true by default
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

		// [1] Get spring All auto configuration classes configured by the factories file
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

		// Remove duplicate configuration classes using LinkedHashSet
		configurations = removeDuplicates(configurations);

		// Get the automatic configuration class to be excluded, such as the configuration class of annotation attribute exclude
		// For example: @ SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
		// You will get exclude = freemarkerautoconfiguration Class annotation data
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);

		// Check the configuration classes to be excluded, because some are not automatic configuration classes, so throw an exception
		checkExcludedClasses(configurations, exclusions);

		// [2] Remove the configuration class to exclude
		configurations.removeAll(exclusions);

		// [3] Because from spring There are too many automatic configuration classes obtained from the factories file. If some unnecessary automatic configuration classes are loaded into memory, it will cause a waste of memory, so it needs to be filtered here
		configurations = filter(configurations, autoConfigurationMetadata);

		// [4] After obtaining the qualified automatic configuration class, the AutoConfigurationImportEvent event event is triggered,
		// The purpose is to tell the ConditionEvaluationReport condition evaluation reporter object to record the automatic configuration classes that meet the conditions
		fireAutoConfigurationImportEvents(configurations, exclusions);

		// [5] Encapsulate the qualified and excluded auto configuration classes into the AutoConfigurationEntry object and return
		return new AutoConfigurationEntry(configurations, exclusions);
	}

In the getCandidateConfigurations method at [1], there is an important method loadFactoryNames, which allows SpringFactoryLoader to load the names of some components.

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		// This method needs to pass in two parameters: getspringfactoryesloaderfactoryclass () and getBeanClassLoader()
		// Getspringfactoryesloaderfactoryclass() this method returns enableautoconfiguration class
		// getBeanClassLoader() this method returns beanClassLoader (class loader)
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

Continue to click the loadFactoryNames method

	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable
			ClassLoader classLoader) {
//Get access key
		String factoryClassName = factoryClass.getName();
		return
				(List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,
						Collections.emptyList());
	}
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result =
				(MultiValueMap)cache.get(classLoader);
		if (result != null) {
			return result;
		} else {
			try {
//If the classloader is not null, spring. Net under the classpath will be loaded The factories file encapsulates the full path information of the configuration class set in it as an Enumeration class object
				Enumeration<URL> urls = classLoader != null ?
						classLoader.getResources("META-INF/spring.factories") :
						ClassLoader.getSystemResources("META-INF/spring.factories");
				LinkedMultiValueMap result = new LinkedMultiValueMap();
//Loop the Enumeration class object, generate the Properties object according to the corresponding node information, obtain the value through the incoming key, cut the value into small strings, convert them into Array, and the method result set
				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()) {
						Entry<?, ?> entry = (Entry)var6.next();
						String factoryClassName =
								((String)entry.getKey()).trim();
						String[] var9 =
								StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
						int var10 = var9.length;
						for(int var11 = 0; var11 < var10; ++var11) {
							String factoryName = var9[var11];
							result.add(factoryClassName, factoryName.trim());
						}
					}
				}
				cache.put(classLoader, result);
				return result;
			}
		}
	}

From the code, we can know that in this method, we will traverse the spring. Net under all jar packages in the whole ClassLoader Factories file. spring. The default auto configuration class provided by springboot is stored in factories.

The getAutoConfigurationEntry method mainly does the following:

[1] From spring Load EnableAutoConfiguration (automatic configuration class) in the factories configuration file, and the obtained automatic configuration class is shown in the figure.

[2] If @ EnableAutoConfiguration and other annotations indicate the auto configuration class to be excluded, then exclude this auto configuration class;

[3] After excluding the auto configuration classes to be excluded, call the filter method for further filtering, and exclude some unqualified auto configuration classes again;

[4] After heavy filtering, the AutoConfigurationImportEvent event event will be triggered to tell the ConditionEvaluationReport condition evaluation reporter object to record the qualified automatic configuration classes;

[5] Finally, return the qualified automatic configuration class.

filter method of AutoConfigurationImportSelector:

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
		long startTime = System.nanoTime();
		// From spring Transfer out string array of automatic configuration class obtained in factories
		String[] candidates = StringUtils.toStringArray(configurations);
		// Defines the skip array and whether it needs to be skipped. Note that the skip array corresponds to the candidates array one by one
		boolean[] skip = new boolean[candidates.length];
		// getAutoConfigurationImportFilters method: get OnBeanCondition, OnClassCondition and OnWebApplicationCondition
		// Then traverse the three condition classes to filter from spring A large number of configuration classes loaded by factories
		boolean skipped = false;
		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
			// Call various aware methods to inject beanClassLoader,beanFactory, etc. into the filter object,
			// The filter object here is OnBeanCondition, OnClassCondition or OnWebApplicationCondition
			invokeAwareMethods(filter);
			// Judge various filter s to judge the value of each candidate (here, we need to get its label through candidate (automatic configuration class)
			// @Whether ConditionalOnClass,@ConditionalOnBean and @ ConditionalOnWebApplication (annotation value) match,
			// Note that the candidates array corresponds to the match array one by one
			/******************************************************/
			boolean[] match = filter.match(candidates, autoConfigurationMetadata);
			// Traverse the match array. Note that the match order corresponds to the automatic configuration class of candidates one by one
			for (int i = 0; i < match.length; i++) {
				// If there is a mismatch
				if (!match[i]) {
					// The mismatched will be recorded in the skip array, and the flag skip[i] is true, which also corresponds to the candidates array one by one
					skip[i] = true;
					// Because of the mismatch, the corresponding autoconfiguration class is left blank
					candidates[i] = null;
					// Mark skipped as true
					skipped = true;
				}
			}
		}
		if (!skipped) {
			return configurations;
		}
		List<String> result = new ArrayList<>(candidates.length);
		for (int i = 0; i < candidates.length; i++) {
			// This means that if all auto configuration classes match after being filtered by OnBeanCondition, OnClassCondition and OnWebApplicationCondition, they will be returned as is
			if (!skip[i]) {
				result.add(candidates[i]);
			}
		}
		// Print log
		if (logger.isTraceEnabled()) {
			int numberFiltered = configurations.size() - result.size();
			logger.trace("Filtered " + numberFiltered + " auto configuration class in "
					+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
		}
		// Finally, return the qualified automatic configuration class
		return new ArrayList<>(result);
	}

The main thing of the filter method of AutoConfigurationImportSelector is to call the match method of the AutoConfigurationImportFilter interface to judge whether the condition annotation (if any) @ conditionalonclass, @ conditionalonbean or @ ConditionalOnWebApplication on each autoconfiguration class meets the conditions. If so, it returns true, indicating matching, If not, false will be returned, indicating the mismatch.

Explanation of condition annotation

@Conditional is a new annotation provided by spring 4. Its function is to judge according to certain conditions and register bean s to the container if the conditions are met.

@ ConditionalOnBean: a Bean will be instantiated only when an object exists in the current context.

@ ConditionalOnClass: a Bean will only be instantiated if a class is on the class path.

@ ConditionalOnExpression: a Bean will be instantiated only when the expression is true. Conditional judgment based on spiel expression.

@ ConditionalOnMissingBean: a Bean will be instantiated only when an object does not exist in the current context.

@ ConditionalOnMissingClass: a Bean will be instantiated only when a class does not exist on the class path.

@ ConditionalOnNotWebApplication: a Bean will only be instantiated if it is not a web application.

@ ConditionalOnWebApplication: instantiate when the project is a Web project.

@ ConditionalOnNotWebApplication: instantiate when the project is not a Web project.

@ ConditionalOnProperty: instantiate when the specified property has a specified value.

@ ConditionalOnJava: trigger instantiation when the JVM version is within the specified version range.

@ ConditionalOnResource: trigger instantiation when there is a specified resource under the classpath.

@ ConditionalOnJndi: trigger instantiation when JNDI exists.

@ ConditionalOnSingleCandidate: trigger instantiation when there is only one specified Bean in the container, or there are multiple beans but the preferred Bean is specified

this. group. How does the selectimports method further selectively import auto configuration classes. Look directly at the code:

public Iterable<Entry> selectImports() {
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			// Here we get the set set of all auto configuration classes to be excluded
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
			// Here we get the set set of all qualified automatic configuration classes after filtering
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			// Remove the autoconfiguration class you want to exclude
			processedConfigurations.removeAll(allExclusions);

			// Sort the auto configuration classes marked with @ Order annotation,
			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

The selectImports method is mainly used for automatic configuration classes that meet the conditions after excluding exclude and filtering by the AutoConfigurationImportFilter interface, and then further exclude the automatic configuration classes of exclude, and then sort them

Summary of the principle of SpringBoot automatic configuration:

1. From spring Load the automatic configuration class in the factories configuration file;

 

2. Exclude the auto configuration class specified by the exclude attribute of the @ EnableAutoConfiguration annotation from the loaded auto configuration class;

 

3. Then use the AutoConfigurationImportFilter interface to filter whether the autoconfiguration class meets the conditions of its annotation (if any) @ conditionalonclass, @ conditionalonbean and @ ConditionalOnWebApplication. If they all meet the conditions, the matching result will be returned;

 

4. Then trigger the AutoConfigurationImportEvent event event and tell the ConditionEvaluationReport condition evaluation reporter object to record the automatic configuration classes that meet the conditions and exclude respectively. 5. Finally, spring imports the auto configuration class after the final filtering into the IOC container

 

Topics: Java Spring Spring Boot Interview ioc