Java and Spring Boot novice, the first attempt to source code analysis, welcome to correct!
I. Overview
1. Implementation of functions: (Spring Boot section)
2. Interface scheduling: (Spring part)
3. The location of the interface in the framework: (one path, top-down)
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 {}; }
(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); } // ...... }
(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.
@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); } } // ...... }
(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.