Spring boot doesn't need much. It's an artifact that liberates both hands of Java development.
The most remarkable feature is de configuration, automatic assembly and automatic configuration. Let developers only focus on business development
Today, let's learn how to realize the source code of automatic assembly
Prepare in advance
- Direct reference to springboot package 2.5.6
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.6</version> </parent>
- Post the web of the previous MVC scanning package XML configuration
<!-- commonly applicationContext There will be scanning package path configuration --> <context:component-scan base-package="com.demo.xxx"></context:component-scan>
In the past, there was an entry to specify the scanning path, but springboot did not have this configuration. It can scan out how it is implemented. Let's find out!
Process Overview
- The entry must be the run method, and the parameters passed in are the current startup class
- Refresh the class in the container before it is initialized, and then refresh the class in the container
- After the container registration phase is completed, all classes that need to be loaded are registered in the BeanDefinitionMap
- Then, calling the processor callback of the registered bean in context, parsing bean in the springboot package.
- The focus is to register a class and get the scanning path when parsing the class, that is, the current path ', This shows that the springboot project scans the startup class layer by default, scans our handwritten code and registers it in the container, but the required dependencies still do not want to be registered in the container
- There is @ import annotation on the startup class, which will be processed together during parsing, and then get the callback of the imported class to get the class that needs to be assembled automatically
- Resolve the classes that need to be loaded automatically in the callback class by reading meta-inf / spring.exe in the file directory in the package Get the value of the corresponding key from the configuration of factories
- Finally, if you want to register the container, the automatic assembly process is over
Source code analysis
The above process can be divided into three steps, step by step analysis.
1. Register the startup class with the container
- The entry is the run() method, which goes directly to the source code
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args){ //primarySource startup class return run(new Class<?>[] { primarySource }, args); }
- Next, run goes down to spring application #run()
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); //Get the listener that needs to be registered SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //Environment and configuration will be discussed later ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); //Print start banner Banner printedBanner = printBanner(environment); //Create web container context = createApplicationContext(); //Set the startup class DefaultApplicationStartup of the web container context.setApplicationStartup(this.applicationStartup); //Make some preparations before container initialization #### let's look at this first prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //Initial container refreshContext(context); //Initialization complete afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } //Notification of container creation completion listeners.started(context); //Callback callrunners implements the callback of the CommandLineRunner interface callRunners(context, applicationArguments); }catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { //Notification that the container has started listeners.running(context); }catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }
- Let's look at the processing before loading the container
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //Some settings of container context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans //Register some single column bean s with the container ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // Load the sources //This is the key point. What is returned in getAllSources() is XXX passed in the run method Class is actually the startup class Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); //From the above, we know that the sources passed in here is actually the startup class. Next, let's see how to load here### load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
- What you get here is the startup class, and what you load is the class, all the way down to beandefinitionloader #load (class <? > source)
private void load(Class<?> source) { if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { // Any GroovyLoaders added in beans{} DSL can contribute beans here GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans()); } //return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type)); //If the class is not empty, is not a Groovy closure, or is not an anonymous class, register directly with the container if (isEligible(source)) { //That is to say, first this.annotatedReader.register(source); } } //this. annotatedReader. The register (source) will then be registered in the IOC container AnnotatedBeanDefinitionReader -> registerBean(componentClass) -> doRegisterBean -> BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
The first point here is to complete. The original intention is to register the startup class in the container, and then use this class in the context callback to complete the scanning of business code, because there is no place to configure which classes under package names spring can scan, similar to completing web Component scan configuration in XML
2. Register the business class with the container
- Next, go back to the original spring application #run and find the following code
//Before container refresh prepareContext(context, environment, listeners, applicationArguments, printedBanner); //Focus on this refresh container### refreshContext(context); //All the way down here protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); } //Next, go to abstractapplicationcontext Refresh() IOC name //Don't say much. Just look at this // Invoke factory processors registered as beans in the context. // In the context, calling the factory processor registered as bean is callback. invokeBeanFactoryPostProcessors(beanFactory); //Down here PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
- Next, go to PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(), and paste only the key code
public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) { // Invoke BeanDefinitionRegistryPostProcessors first, if any. Set<String> processedBeans = new HashSet<>(); if (beanFactory instanceof BeanDefinitionRegistry) { //Omit some codes ...... // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered. //Call implementation class //Also note that the breakpoint debugging spring package implements only one of this class //Configuration class postprocessor String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { //Add to list currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); //Callback for the registered bean. Next, see here### invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup()); //Omit some codes ...... } //invokeBeanDefinitionRegistryPostProcessors private static void invokeBeanDefinitionRegistryPostProcessors( Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) { for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process") .tag("postProcessor", postProcessor::toString); //Start to callback the bean that implements the BeanDefinitionRegistryPostProcessor interface postProcessor.postProcessBeanDefinitionRegistry(registry); postProcessBeanDefRegistry.end(); } }
- After breakpoint debugging, it will go to spring-context-5.3.12 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry under jar package
- One thing to note is that if the startup class is registered manually, there must be a startup class in candidate names. We ignore other classes and mainly focus on the scanning and loading of startup classes
//All the way down here public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } //Determine whether the @ Configuration annotation is added else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { //If it is satisfied, it will be added to the list. The startup class here must have added this annotation and can be verified by itself configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } //Omit some codes ...... // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { //Start parsing the @ Configuration annotated class #### and look at this first parser.parse(candidates); parser.validate(); //Get the class that needs to be loaded after parsing Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } //Register the class to be loaded into the container this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); //Omit some codes ...... }
- Take a look at parser parse(candidates); What did you do? Go to ConfigurationClassParser#parse()
ublic void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { //The startup class annotates @ Configuration, which belongs to annotation definition if (bd instanceof AnnotatedBeanDefinition) { //Next, go here## 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); } } //Analyze the process after parse this.deferredImportSelectorHandler.process(); }
- Down to the real working class, ConfigurationClassParser#doProcessConfigurationClass parses the core class
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { //Parse @ Component annotation if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass, filter); } //@PropertySource annotation // Process any @PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } //@ComponentScan annotation scans the corresponding package path //@This annotation is available on the SpringBootApplication, or you can specify it yourself // Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately //Find out if the ComponentScan annotation uses value //Next, let's see how to scan#### Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } //@After the Import annotation is analyzed and scanned, the next step is to focus on here### // Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), filter, true); //@ImportResource annotation // Process any @ImportResource annotations AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } //@Bean annotation method // Process individual @Bean methods Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces processInterfaces(configClass, sourceClass); // 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; }
- @ComponentScan specifies the scan package path, ConfigurationClassParser#doProcessConfigurationClass
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); -> ComponentScanAnnotationParser.parse //To this method public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { //Omit some codes ...... Set<String> basePackages = new LinkedHashSet<>(); //Get the manually written scan package path, which is the package path written in the annotation String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } //We didn't write the scan path manually here, so we will go here if (basePackages.isEmpty()) { //Next, look here //public static String getPackageName(String fqClassName) { //Assert.notNull(fqClassName, "Class name must not be null"); //int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR); //return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : ""); //} // Then found the private static final char PACKAGE_SEPARATOR = '.'; //What you get is the package name path of the startup class, which also verifies that the springboot project scans the startup class layer by default basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); //Scan the class files under the specified package path. Next, I'll continue to look down### return scanner.doScan(StringUtils.toStringArray(basePackages)); }
- Next, go to ClassPathBeanDefinitionScanner#doScan()
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { //Look down and find here### Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { //Callback because the container has handled the callback step, the newly registered bean needs to callback itself postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //Register with the container. I won't say much here registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
- Next, go to classpathscanningcandidatecomponentprovider #findcandidatecomponents - > scancandidatecomponents
//This is the familiar reading stream private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { //resourcePattern = **/*. Class suffix String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; //Read stream Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); //In fact, all annotations that need to be loaded implement @ Component if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } //Scan the class file under the corresponding path into the list, and then register it with the container candidates.add(sbd); } .... //Omit some codes return candidates; }
So far, we have registered our own business code into the container, but the bean s that the springboot framework needs to rely on are still not registered. Next, let's see how to implement automatic assembly
3. Register the auto assembled classes that you need to rely on with the container
- Let's go back to configurationclassparser In the doprocessconfigurationclass method
//Find this line to parse the @ import annotation // Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), filter, true); //Next, go to the processImports method. Only those annotated with @ Import will be included for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); Predicate<String> selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { exclusionFilter = exclusionFilter.or(selectorFilter); } //Only the implementation of DeferredImportSelector can go here //Therefore, find the annotation on the startup class, which is only introduced in @ EnableAutoConfiguration //@Import(AutoConfigurationImportSelector.class) //DeferredImportSelector inherits ImportSelector if (selector instanceof DeferredImportSelector) { //Next, go here### this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); processImports(configClass, currentSourceClass, importSourceClasses, false); } } ...//Omit some codes }
- Next, go to the ConfigurationClassParser#handler method and the internal class DeferredImportSelectorHandler
//Post the two methods of this class private class DeferredImportSelectorHandler { @Nullable private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>(); public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) { DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector); //The use of null local variable lock is only in process, that is, in process if (this.deferredImportSelectors == null) { DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler(); handler.register(holder); //recursion handler.processGroupImports(); } else { //Normally, it will not be null and added to this list this.deferredImportSelectors.add(holder); } } 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); //Callback according to the added ImportSelector class. Here's the next step### handler.processGroupImports(); } } finally { this.deferredImportSelectors = new ArrayList<>(); } } }
The above analysis has obtained all the class collections introduced with @ Import and implemented DeferredImportSelector
- Return to configurationclassparser #parse again (set < beandefinitionholder > configcandidates)
//Find the last line of code for the method this.deferredImportSelectorHandler.process(); //Go to the internal class DeferredImportSelectorHandler#process above, and then go to this line of code handler.processGroupImports(); public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { Predicate<String> exclusionFilter = grouping.getCandidateFilter(); //First look at getImports()### grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata()); try { //Here is another way to handle the import annotation, but the judgment is different processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter), Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)), exclusionFilter, false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } }
- ConfigurationClassParser#getImports()
public Iterable<Group.Entry> getImports() { for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { //Get the class that needs to be loaded automatically### this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } //After processing, return the class of the required automatic device return this.group.selectImports(); }
- This group will be connected to the internal class AutoConfigurationGroup#process of AutoConfigurationImportSelector
@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())); //Resolve the classes that need to be automatically assembled. Next, let's see how to find the classes that need to be resolved### AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(annotationMetadata); //Save it in the following selectImports and return it after processing this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } } //Get the follow-up of the automatically assembled class and don't post it later @Override public Iterable<Entry> selectImports() { if (this.autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } //classname to exclude Set<String> allExclusions = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet()); //list collection of classname s that need to be automatically assembled Set<String> processedConfigurations = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream) .collect(Collectors.toCollection(LinkedHashSet::new)); //remove processedConfigurations.removeAll(allExclusions); //Sort and encapsulate return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream() .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) .collect(Collectors.toList()); } } //getgetAutoConfigurationEntry -> getCandidateConfigurations //To AutoConfigurationImportSelector //Obtain the class to be automatically assembled for the specified key through classLoader List<String> configurations = //getSpringFactoriesLoaderFactoryClass return EnableAutoConfiguration.class; //The key here is the class path of auto assembly EnableAutoConfiguration SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } //The key is the EnableAutoConfiguration class path //org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryTypeName = factoryType.getName(); //Here, get the contents of the automatic assembly of the corresponding class loader from the cache, and then take out the value of the key, which returns an empty list by default return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
- Next, spring factoriesloader #loadspring factories, get all classes that need automatic devices and store them in the cache
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { //Get from cache first Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { //String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; //From the path, we know that it should be read from the specified file. Next, let's look at the corresponding file Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); //The map key saved to the corresponding class loader is the corresponding class loader cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
- Only the springboot package is cited here, so look for meta-inf / spring.exe in the spring boot autoconfigure package directory Find the corresponding key in the factories file
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ ......
- After corresponding to the classes that need to be assembled automatically, go back to ConfigurationClassParser#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 { //We will continue to scan and load classes recursively### processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter), Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)), exclusionFilter, false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } }
- Next, go back to ConfigurationClassParser#processImports, because it is no longer an ImportSelector at this time. Find the else
else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class //Is not the direction of the implementation class of ImportSelector this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); //Load into local Map store processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); } //processConfigurationClass last //Store to local map this.configurationClasses.put(configClass, configClass);
The classes that need to be automatically assembled already exist in the local map, and the next step should be to register in the late container
- Back to ConfigurationClassPostProcessor#processConfigBeanDefinitions
- The previous code is actually analyzing parser parse(candidates);, Next, go to the back line
parser.parse(candidates); parser.validate(); //public Set<ConfigurationClass> getConfigurationClasses() { // return this.configurationClasses.keySet(); // } //Retrieve and store the class to be registered return this configurationClasses. keySet(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Take out the reader and create it without // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } //Register with container this.reader.loadBeanDefinitions(configClasses);
Here, the whole automatic assembly process is over. The source code is relatively flexible, so you need to be patient
That is the whole content of this chapter.
Previous: The sixth bullet of SpringMvc source code analysis - handwritten high imitation version based on SpringMvc source code
Next: Springboot source code analysis second bullet - automatic configuration implementation
The east corner is dead, sang Yu is not late