Spring Boot Auto-Configuration: In‑Depth Source Code Walkthrough
This article explores the complete execution flow of Spring Boot’s auto‑configuration, detailing the role of @EnableAutoConfiguration, the AutoConfigurationImportSelector hierarchy, how candidate configurations are loaded from spring.factories, filtered by conditions, and how to create custom starters, with interview‑style Q&A.
1. Auto‑Configuration Entry Recap
Previously we covered the composition of @SpringBootApplication and identified @EnableAutoConfiguration as the entry point for auto‑configuration.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}Core: The @Import(AutoConfigurationImportSelector.class) imports AutoConfigurationImportSelector.
2. AutoConfigurationImportSelector Source Analysis
2.1 Class inheritance hierarchy
ImportSelector (interface)
│
└── DeferredImportSelector (interface, delayed import)
│
└── AutoConfigurationImportSelector DeferredImportSelectorexecutes after all @Configuration classes have been parsed, ensuring user‑defined beans can override auto‑configured ones.
2.2 selectImports method
// AutoConfigurationImportSelector.java
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. Check if auto‑configuration is enabled (default true)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 2. Load auto‑configuration metadata
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
// 3. Return the array of configuration class names
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}2.3 getAutoConfigurationEntry method
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 1. Check if auto‑configuration is enabled
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 2. Retrieve annotation attributes (exclude, excludeName)
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3. Load all candidate configuration classes from spring.factories
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 4. Remove duplicates
configurations = removeDuplicates(configurations);
// 5. Process exclude items
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
configurations.removeAll(exclusions);
// 6. Conditional filtering (@Conditional annotations)
configurations = filter(configurations, autoConfigurationMetadata);
// 7. Trigger auto‑configuration import events
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}2.4 Loading candidates from spring.factories
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// Key: load from spring.factories
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories");
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
// Returns EnableAutoConfiguration.class
return EnableAutoConfiguration.class;
}2.5 Conditional filtering
private List<String> filter(List<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
// Candidate list
String[] candidates = StringUtils.toStringArray(configurations);
boolean[] skip = new boolean[candidates.length];
// Iterate each candidate and check conditions
for (int i = 0; i < candidates.length; i++) {
String candidate = candidates[i];
// Use metadata to see if @Conditional is present
if (autoConfigurationMetadata.wasProcessed(candidate)) {
// Skip if conditions are not satisfied
skip[i] = !autoConfigurationMetadata.getConditions(candidate).isEmpty();
}
}
// Collect non‑skipped configurations
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
return result;
}3. How Conditional Annotations Work
3.1 Core interface
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}3.2 Example: @ConditionalOnClass
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}3.3 OnClassCondition core logic
public class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// Get annotation attributes
MultiValueMap<String, Object> attributes =
metadata.getAllAnnotationAttributes(ConditionalOnClass.class.getName(), true);
// Classes that must be present
List<String> onClasses = getClasses(attributes, "value");
// Classes that must be missing
List<String> onMissingClasses = getClasses(attributes, "name");
// Check classpath
ClassLoader classLoader = context.getClassLoader();
// (Implementation details omitted for brevity)
return new ConditionOutcome(match, message);
}
}3.4 Execution flow
Auto‑configuration class
▼
Parse @Conditional annotations
▼
Instantiate corresponding Condition class (e.g., OnClassCondition)
▼
Call matches()
├── Condition satisfied → load configuration class
└── Condition not satisfied → skip configuration class4. Spring Boot 2.7 New Feature: Auto‑Configuration File Location Change
Before 2.7 the list of auto‑configuration classes lived in META-INF/spring.factories. Starting with 2.7 the format is gradually deprecated in favour of
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, where each line contains a single fully‑qualified configuration class.
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfigurationThis one‑line‑per‑class format is more concise.
5. Custom Starter Practical Guide
5.1 Create auto‑configuration class
// Configuration properties class
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
private String prefix = "Hello";
private String suffix = "!";
// getters/setters omitted
}
// Service class
public class HelloService {
private String prefix;
private String suffix;
public HelloService(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
}
public String sayHello(String name) {
return prefix + " " + name + suffix;
}
}
// Auto‑configuration class
@Configuration
@ConditionalOnClass(HelloService.class)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public HelloService helloService(HelloProperties properties) {
return new HelloService(properties.getPrefix(), properties.getSuffix());
}
}5.2 Register the starter
For Spring Boot 2.7+ create
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: com.example.hello.HelloAutoConfiguration For earlier versions add the class name to spring.factories under the EnableAutoConfiguration key.
5.3 Use the starter
In application.yml:
hello:
prefix: "你好"
suffix: "~"Inject and call:
@Autowired
private HelloService helloService;
helloService.sayHello("老J"); // → 你好 老J~6. Common Interview Questions
Q1: What is the execution order of auto‑configuration?
Answer: AutoConfigurationImportSelector implements DeferredImportSelector, so it runs after all regular @Configuration classes are processed. Ordering can be further controlled with @AutoConfigureOrder, @AutoConfigureAfter and @AutoConfigureBefore.
Q2: How do conditional annotations work?
Each @Conditional links to a Condition implementation. Spring Boot invokes the matches() method; for example @ConditionalOnClass checks classpath presence, while @ConditionalOnMissingBean checks the bean registry.
Q3: How to create a custom starter?
Write an auto‑configuration class annotated with @ConfigurationProperties to define configurable properties. Use @Conditional (e.g., @ConditionalOnClass ) to control when the configuration is applied. Register the class in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (or spring.factories for pre‑2.7). Add the starter dependency to the consuming project.
7. One‑Page Summary Diagram
@SpringBootApplication
│
└── @EnableAutoConfiguration
│
└── @Import(AutoConfigurationImportSelector)
▼
selectImports()
▼
getAutoConfigurationEntry()
┌───────────────┼───────────────┐
▼ ▼ ▼
Load spring.factories Process exclude @Conditional filtering
│ │ │
└───────────────┴───────────────┘
▼
Return eligible configuration classes
▼
Spring container registers them
▼
@Bean definitions become active8. Next Issue Preview
Spring Boot source analysis (Part 4): Deep dive into IoC container initialization, covering the 12 steps of refresh(), bean creation flow, three‑level cache for circular dependencies, and essential interview topics.
9. Interaction
Feel free to ask questions about auto‑configuration, conditional annotations, custom starters, or ordering issues in the comments.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
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.
