Deep Dive into Spring Boot Auto‑Configuration Mechanism
This article examines Spring Boot's auto‑configuration process by analyzing the source code of @SpringBootApplication, @EnableAutoConfiguration, and related classes, explaining how the framework loads configuration classes via SpringFactoriesLoader, processes deferred import selectors, and integrates with the application context during startup.
Spring Boot's auto‑configuration is the foundation of its "convention over configuration" approach and a prerequisite for building micro‑services. This article walks through the actual implementation by inspecting the source code of the key annotations and classes involved.
1. Auto‑configuration Process Analysis
1.1 @SpringBootApplication
When we write a Spring Boot project, @SpringBootApplication is the most common annotation. Its source code combines several other annotations:
package org.springframework.boot.autoconfigure;
import java.lang.annotation.*;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.*;
/**
* Indicates a {@link Configuration configuration} class that declares one or more
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
* auto‑configuration} and {@link ComponentScan component scanning}. This is a
* convenience annotation that is equivalent to declaring {@code @Configuration},
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
Class
[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class
[] scanBasePackageClasses() default {};
}From the source we can infer that @SpringBootApplication is equivalent to @Configuration @ComponentScan @EnableAutoConfiguration .
1.2 @EnableAutoConfiguration
Adding this annotation enables auto‑configuration. Spring attempts to locate all configuration beans on the classpath and apply them, respecting various conditional rules.
package org.springframework.boot.autoconfigure;
import java.lang.annotation.*;
import org.springframework.context.annotation.*;
/**
* Enable auto‑configuration of the Spring Application Context, attempting to guess and
* configure beans that you are likely to need.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto‑configuration classes such that they will never be applied.
*/
Class
[] exclude() default {};
/**
* Exclude specific auto‑configuration class names such that they will never be applied.
*/
String[] excludeName() default {};
}The selector used by this annotation is EnableAutoConfigurationImportSelector , which extends AutoConfigurationImportSelector and ultimately implements DeferredImportSelector .
1.3 AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final String[] NO_IMPORTS = {};
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List
configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set
exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
// ... other helper methods omitted for brevity ...
}The selector reads META-INF/spring.factories to obtain candidate auto‑configuration classes, applies exclusions and filters, and finally returns the list of classes to import.
1.4 DeferredImportSelector and ImportSelector
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
public interface DeferredImportSelector extends ImportSelector {
// additional contract for deferred processing
}Deferred import selectors are processed after all @Configuration classes have been parsed, ensuring that imports can depend on the full set of configuration metadata.
2. When Auto‑Configuration Is Applied
The processing starts in AbstractApplicationContext.refresh() , which invokes all BeanFactoryPostProcessor beans:
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);One of the key post‑processors is ConfigurationClassPostProcessor , which implements BeanDefinitionRegistryPostProcessor (and thus BeanFactoryPostProcessor ). It parses @Configuration classes, registers bean definitions, and handles deferred import selectors.
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook not supported – process lazily
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}The core parsing logic resides in ConfigurationClassParser.parse() , which iterates over candidate configuration classes, reads their metadata, and ultimately calls processDeferredImportSelectors() to handle any DeferredImportSelector instances (such as EnableAutoConfigurationImportSelector ).
private void processDeferredImportSelectors() {
List
deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
} catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
}
}Thus, during the refresh phase, Spring Boot loads the auto‑configuration classes, applies any exclusions, and registers the resulting beans before the regular bean instantiation begins.
3. Summary
1) Auto‑configuration relies on SpringFactoriesLoader to read META-INF/spring.factories , gathering all EnableAutoConfiguration entries, then filters and excludes them to determine the final set of classes to import.
2) The handling of @Configuration is performed by ConfigurationClassPostProcessor , which is a BeanFactoryPostProcessor . When AbstractApplicationContext.refresh() invokes invokeBeanFactoryPostProcessors , the auto‑configuration logic is executed, parsing configuration classes, processing deferred import selectors, and ultimately registering the auto‑configured beans.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.