A brief introduction to the principle of springboot starter

A brief overview of the principle

  • The various starter s of springboot are simply to load the configuration classes @Configuration in various dependency packages. These configuration classes must be org.springframework.boot.autoconfigure.EnableAutoConfiguration in the spring.factories file in the resources/META-INF directory of the module. declared in the corresponding value
  • Because it is impossible for us to declare in the startup class that the scanned package contains the configuration class @Configuration of all dependent packages, it is unrealistic. So spring provides a mechanism to load these configuration classes @Configuration. As long as these configuration classes can be read, they are parsed in the same way as the configuration class @Configuration declared in the project.

Summary: Because I don't want to declare the scanned packages corresponding to the configuration class @Configuration in various dependency packages, because there are too many and too messy, and I want spring to manage these configuration classes @Configuration, I use other ways to read these configuration classes.

Source code analysis

(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

exist Introduction to spring @Import This article has explained the usage of @Import and that AutoConfigurationImportSelector is of type DeferredImportSelector (deferred ImportSelector). The following descriptions are all based on this.

For DeferredImportSelector, it will run after spring has processed our custom @Configuration bean (why? Because most of the auto-configuration classes have @Condition, for example, if the user has already defined a bean, then the auto-configuration will not effective). Here you need to understand what the ConfigurationClassPostProcessor class has done as a BeanFactoryPostProcessor, which will not be introduced here. you can see me before
ConfigurationClassPostProcessor This article. Just look at the specific code here.

ConfigurationClassParser#parse simplified code, the last line of code is to deal with DeferredImportSelector.

	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
		}
		this.deferredImportSelectorHandler.process();
	}

exist Introduction to spring @Import This article has already said that for DeferredImportSelector, it was packaged as DeferredImportSelectorHolder and then placed in a list collection of deferredImportSelectorHandler. Here, it is enough to directly parse this collection of deferredImportSelectorHandler.

DeferredImportSelectorHandler#process

		public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
					deferredImports.forEach(handler::register);
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}

Here is to declare a DeferredImportSelectorGroupingHandler, and then call its register method in a loop to process these DeferredImportSelectorHolder s. Here first look at the register method.

	private class DeferredImportSelectorGroupingHandler {
		private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
		private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();

		public void register(DeferredImportSelectorHolder deferredImport) {
			Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
			DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
					(group != null ? group : deferredImport),
					key -> new DeferredImportSelectorGrouping(createGroup(group)));
			grouping.add(deferredImport);
			//1
			this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
					deferredImport.getConfigurationClass());
		}
  • Gets the group to which the ImportSelector in the DeferredImportSelectorHolder belongs. The following is the source code of AutoConfigurationImportSelector to get the group.
	@Override
   public Class<? extends Group> getImportGroup() {
   	return AutoConfigurationGroup.class;
   }
  • Get the corresponding DeferredImportSelectorGrouping from the grouping property, create it if not, and then add the DeferredImportSelectorHolder to the group. Note the createGroup method here, which will be used later to return the DeferredImportSelectorGrouping parameter. A simple understanding is that spring groups DeferredImportSelector. Here is a collection of the same group.
  • The last line of code maps annotation properties to ConfigurationClass, which needs to be read in conjunction with subsequent processing (1 code)

After the register is completed, the real processing handler.processGroupImports()

		public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false);
					}
				});
			}
		}

You can see that it is processed after grouping according to the register method. The source code of grouping.getImports() is as follows

		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}
  • Loop through DeferredImportSelectorHolder using the group member variable. As mentioned above, the group of AutoConfigurationImportSelector is AutoConfigurationGroup, so createGroup in the above register method will create an instance of AutoConfigurationGroup, which is the value of the group member variable here.

AutoConfigurationGroup#process source code

	public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
	//AutoConfigurationEntry contains the full class names of those auto-configuration classes, and of course excluded classes.
		AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
				.getAutoConfigurationEntry(annotationMetadata);
		this.autoConfigurationEntries.add(autoConfigurationEntry);
		for (String importClassName : autoConfigurationEntry.getConfigurations()) {
			this.entries.putIfAbsent(importClassName, annotationMetadata);
		}
	}

Obtaining AutoConfigurationEntry will call the AutoConfigurationImportSelector#getAutoConfigurationEntry method, which will be followed by the following calls

	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
				
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

Is not very familiar. I saw EnableAutoConfiguration.class and SpringFactoriesLoader.loadFactoryNames. If you are not familiar with SpringFactoriesLoader.loadFactoryNames, you can read other articles or my article. springboot2.1.7 startup analysis (1) SpringApplication instantiation , which is not explained here.

Then look at the last line of the grouping.getImports() source code: return this.group.selectImports() part of the source code

		public Iterable<Entry> selectImports() {
		...
			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

Simply put, it is to wrap the configuration class into an Entry return.

		class Entry {
			private final AnnotationMetadata metadata;
			private final String importClassName;
			public Entry(AnnotationMetadata metadata, String importClassName) {
				this.metadata = metadata;
				this.importClassName = importClassName;
			}

The metadata in the Entry is the same key in the configurationClassesd (map type) in the last line of code in the register method above, so the obtained ConfigurationClass is the same. Both are ConfigurationClass in DeferredImportSelectorHolder.

				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false);
					}
				});

The processImports method is in Introduction to spring @Import As explained in , the third parameter of this method is the value corresponding to the candidate @import. Here is the parameter that wraps entry.getImportClassName(), the configuration class. This is equivalent to the processing of @import values, because these configuration classes should not be of type ImportSelector or ImportBeanDefinitionRegistrar, and will be processed as @Configuration classes.

Summarize

Here is just a brief description of when and how spring reads these auto-configuration classes and treats them as @Configuration classes. Many details are omitted in the middle. This article only focuses on how those effective auto-configuration classes are loaded by spring of. Reprinted: The mystery of automatic assembly This article goes into more detail.

Tags: Java Spring Spring Boot

Posted by depojones on Fri, 06 May 2022 10:15:12 +0300