Processing of @Enable AutoConfiguration Annotation @AutoConfiguration Import Selector Annotation

Keywords: Java Spring Attribute SpringBoot

Java and Spring Boot novice, the first attempt to source code analysis, welcome to correct!

 

I. Overview

@ The Enable AutoConfiguration annotation is the master switch configured for automatic loading in Spring Boot. This article will start with @Enable AutoConfiguration and try to enhance Spring Boot understanding through source code analysis.
 
Version used: Spring Boot 2.2.0.M5 + Spring Framework 5.2.0.RC1
 

1. Implementation of functions: (Spring Boot section)

Boot. autoconfigure. Enable AutoConfiguration annotation
->@ Import An AutoConfiguration Import Selector Example
-> The AutoConfiguration ImportSelector class (implementation ImportSelector) implements the selectImports() method to filter the Configuration class (minus exclude, etc.) that is @Import.

 

2. Interface scheduling: (Spring part)

parse() method of context. annotation. Configuration ClassParser class
-> Call the parse() method corresponding to different BeanDefinition types
|-> Call context. annotation. Configuration ClassParser. doProcessConfiguration Class () method to process Configuration Class
|-> Call the processImports() method to process all @Import annotations
|-> Traverse through each @Import tag to generate an instance of the injected ImportSelector subclass
|-> For ordinary Import Selector, call its selectImport() method, filter out exclude, and nest processImports() to process the @Import annotation of the class that needs to be @Import
|-> For Deffered Import Selector, add only the deferred Import Selectors list
-> Processing defferedImportImport Selectors calling process() method of corresponding handler
-> Call processImports() on DefferedImportImport Selector

 

3. The location of the interface in the framework: (one path, top-down)

[Spring Book Part]
boot.SpringApplication.main() or Application Starter. main ()
boot.SpringApplication.run()
boot.SpringApplication.refreshContext()
boot.SpringApplication.refresh()
boot.web.servlet.context.ServletWebServerApplicationContext.refresh()
[Spring Part]
context.support.AbstractApplicationContext.refresh()
context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors()
context.support.PostPreprocessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory()
context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions()
Context. annotation. Configuration ClassParser. parse () (the interface described in the previous summary)

 

Source code details

(SpringBoot) Boot. autoconfigure. Enable AutoConfiguration annotation

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // Load selector, identify AutoConfigutaion class and import
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	// @ interface parameters, declared in method form
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}
 
@ The EnableAutoConfiguration annotation has two parameters and one attribute variable.
An instance of the AutoConfiguration ImportSelector class was injected, which seems to be used to filter the AutoConfiguration Import.
@ AutoConfiguration Package will be analyzed later.

 

(SpringBoot) boot. autoconfigure. AutoConfiguration ImportSelector class

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    //......
    @Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		// If AutoConfiguration is not open, return {}
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		// Load the key-value pair configuration of spring-autoconfigure-metadata.properties into the Properties AutoConfiguration Metadata object and return
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
        // Configuration and exclusion for import based on various configuration calculations
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
            
    // Determine whether Audo Configuration is on
	protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass() == AutoConfigurationImportSelector.class) {
			// If there is "spring.boot.enableautoconfiguration" in the configuration file, return the value of the field; otherwise return true
			return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
		}
		return true;
	}
            
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// Get the attribute value of the annotation
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// Get the configurations corresponding to Enable AutoConfiguration from the META-INF/spring.factories file
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		// Deduplicate, List to Set and List to List
		configurations = removeDuplicates(configurations);
		// Get the exclusion from the exclude/excludeName attribute of the annotation
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// Error reporting for exclude that does not belong to AutoConfiguration
		checkExcludedClasses(configurations, exclusions);
		// Remove exclusions from configurations
		configurations.removeAll(exclusions);
		// All instances of the AutoConfiguration ImportFilter class are filtered again to
		configurations = filter(configurations, autoConfigurationMetadata);
		// Bind AutoConfiguration ImportEvent to all AutoConfiguration ImportListener subclass instances
		fireAutoConfigurationImportEvents(configurations, exclusions);
		// Return to (configurations, exclusions) group
		return new AutoConfigurationEntry(configurations, exclusions);
	}
    // ......
}

 

It can be seen that selectImports() is the core function of AutoConfiguration Import Selector. Its core function is to obtain the list of Configuration classes corresponding to EnableAutoConfiguration in spring.factories. It is filtered by exclude/excludeName parameter in @EnableAutoConfiguration annotation, and then filtered by all instances of AutoConfiguration ImportFilter class to get the final list of Configuration classes for Impor. T's configuration and exclusion.
Who called this function? ProceImports () is called in the org. spring framework. context. annotation. Configuration ClassParser class, and the processImports() function is called by doProcessConfiguration Class (). Let's start with doProcessConfiguration Class ().
 
 

(Spring) context. annotation. Configuration ClassParser. doProcessConfiguration Class () method

Among them, configClass is an instance of Configuration Class, which records the bean name (returned bean name) and meta data (configuration data). SorceClass is a simple encapsulated annotated class, mainly convenient for the use of class annotations, and the initial value is encapsulated configClass.

DoProcessConfiguration Class () has various configurations for Configuration Class, including process@ComponentScan, process@Bean, process@Import and so on. If the SourceClass has a parent, return the parent, otherwise return null.

 

        @Nullable
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {
		// ......
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true); 

		// ......
		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}   

 

In the third parameter of the processImports() method, the getImports() method nests the annotations of the sourceClass and collects all the values of the @Import annotations, i.e. the set of class names by Import.

 

	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}

	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {
        // If it's the first addition
		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
                // Is the class name of the current annotation Import
				if (!annName.equals(Import.class.getName())) { 
                    // Nested traversal of Import classes
					collectImports(annotation, imports, visited);
				}
			}
            // Increase the value of the Import annotation, which is the class name of the Import
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}

 

(Spring) context. annotation. Configuration ClassParser. processImports () method

The first parameter configClass of processImports() is the only parameter of the upper function processConfiguration Class (), which is the processed Configuration class. The second parameter, currentSourceClass, is the SourceClass class encapsulation of configClass. The third parameter is the nested traversal of all the classes that need to be Import. The fourth parameter specifies whether to check the loop import.

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

		// ......
				for (SourceClass candidate : importCandidates) {
                    // If candidate (that is, the class by @Import) is a subclass of ImportSelector
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
                        // Generate an instance of candidate class (an ImportSelector subclass)
						ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        // Instances of ImportSelector subclasses are mounted as Aware classes for corresponding functions (for message notification?)
						ParserStrategyUtils.invokeAwareMethods(
								selector, this.environment, this.resourceLoader, this.registry);
                        // Deferred Import Selector is a special Import Selector, which is handled separately here
						if (selector instanceof DeferredImportSelector) {
                            // Hang onto the deferred Import Selectors list
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
                            // Screening for the required @Import name
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            // Converting to a collection of classes corresponding to a name
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            // Nested to determine if the class being @Import has an @Import annotation
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}
					// ......
				}
		// ......
	}

 

(Spring) context. annotation. Configuration ClassParser. DefferedImportSelector Handler private class

Deferred Import Selectors have been initialized to ArrayList <>(), so they all go to the else branch.

private class DeferredImportSelectorHandler {
		@Nullable
		private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();

		public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
			DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
					configClass, importSelector); // An encapsulated pair
            // Deferred Import Selectors are initialized to ArrayList <>(), so if branches never execute?
			if (this.deferredImportSelectors == null) { 
				DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
				handler.register(holder); // Register in Deferred Import Selector Grouping Handler. Configuration Classes
				handler.processGroupImports(); // -> processImports()
			}
			else {
                // All DeferredImportSelector class instances are hung on the deferredImportSelectors list
				this.deferredImportSelectors.add(holder);
			}
		}
        // ......
}

  

So when is the DeferedImportSelector class mounted on deferredImportSelectors handled?
The doProcessConfigurationClass() method is called by processConfigurationClass(), while processConfigurationClass() is called by parse(). You can see that the DefferedImportSelector class is processed uniformly after all the ordinary ImportSelector classes are processed, that is, after the instances of the required Import class are nested and loaded.
 

(Spring) context. annotation. Configuration ClassParser. processConfiguration Class () method

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        // ...

        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            // If the sourceClass has a parent, it will return the parent, otherwise it will return null.
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);
    }

  

(Spring) context. annotation. Configuration ClassParser. parse () method

        public void parse(Set<BeanDefinitionHolder> configCandidates) {
    	// Processing all ImportSelector classes, where the DeferredImportSelector class is only hung on the deferredImportSelector Handler list and not processed, the others are processed, that is, nested traversal of the Import class
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}
		
    	// Handling the DeferredImportSelector class hanging on deferredImportSelectorHandler
		this.deferredImportSelectorHandler.process();
	}

	protected final void parse(@Nullable String className, String beanName) throws IOException {
		Assert.notNull(className, "No bean class name for configuration class bean definition");
		MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
		processConfigurationClass(new ConfigurationClass(reader, beanName));
	}

	protected final void parse(Class<?> clazz, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(clazz, beanName));
	}

	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(metadata, beanName));
	}

  

Let's see how the DeferredImportSelector instance is handled.

 

(Spring) context. annotation. Configuration ClassParser. DefferedImportSelector Handler private class

Like ordinary Configuration Class, Deffered Import Selector is finally registered in the list and nested in turn, except that there is an additional order ing before import.

	// Sort @Order(...)
	private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
			(o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector());

	private class DeferredImportSelectorHandler {
    	// ...
    	public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                    // Add to the configurationClasses list of handler
					deferredImports.forEach(handler::register);
                    // For each configClass of each grouping in handler, call processImports()
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}
	}

 

DONE.

Posted by ghjr on Wed, 09 Oct 2019 18:35:56 -0700