Spring Boot Auto-Configuration Internals and Mechanisms

Auto-Configuration Overview

Auto-configuration dynamically registers beans into the Inversion of Control (IoC) container based on the jar dependencies present on the classpath. These beans can then be injected using annotations like @Autowired or @Resource.

Decomposing the @SpringBootApplication Annotation

The entry point of a Spring Boot application is the main method annotated with @SpringBootApplication. This annotation is a composite annotation encapsulating three core annotations:

@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 SpringBootApp {
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
}

@SpringBootConfiguration

Internally, this annotation is simply a wrapper around Spring's @Configuration. It signifies that the class is a configuration class and allows the component scanner to detect it.

@EnableAutoConfiguration

This annotation triggers the auto-configuration mechanism, relying heavily on @Import to gather and register specific beans.

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

@AutoConfigurationPackage

This annotation imports AutoConfigurationPackages.Registrar. The registrar captures the package name of the main application class and stores it as a BasePackages bean definition. This stored package path is later utilized by frameworks like JPA to scan for entity classes.

public void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry reg) {
    register(reg, new PackageImport(meta).getPackageName());
}

AutoConfigurationImportSelector

The AutoConfigurationImportSelector implements DeferredImportSelector and is responsible for loading all eligible @Configuration classes into the IoC container. The execution flow begins in the getImports() method, which delegates to AutoConfigurationGroup.process(), ultimately invoking getAutoConfigurationEntry().

The getAutoConfigurationEntry Logic

This method evaluates and filters candidate configuration classes to prevent unnecessary memory consumption.

protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigMetadata,
        AnnotationMetadata annotationMeta) {
    if (!isEnabled(annotationMeta)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attrs = getAttributes(annotationMeta);
    // 1. Retrieve candidate configurations from spring.factories
    List<String> candidateConfigs = getCandidateConfigurations(annotationMeta, attrs);
    candidateConfigs = removeDuplicates(candidateConfigs);
    // 2. Determine exclusions specified via @SpringBootApplication(exclude)
    Set<String> excludedConfigs = getExclusions(annotationMeta, attrs);
    checkExcludedClasses(candidateConfigs, excludedConfigs);
    candidateConfigs.removeAll(excludedConfigs);
    // 3. Filter using AutoConfigurationImportFilter (e.g., @ConditionalOnClass)
    candidateConfigs = filter(candidateConfigs, autoConfigMetadata);
    // 4. Fire events for ConditionEvaluationReport
    fireAutoConfigurationImportEvents(candidateConfigs, excludedConfigs);
    return new AutoConfigurationEntry(candidateConfigs, excludedConfigs);
}

Loading from spring.factories

The getCandidateConfigurations method delegates to SpringFactoriesLoader.loadFactoryNames(). The loader scans the classpath for all META-INF/spring.factories files, parses them as properties, and retrieves the fully qualified class names mapped to EnableAutoConfiguration.

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // Cache check omitted for brevity
    Enumeration<URL> urls = classLoader != null 
        ? classLoader.getResources("META-INF/spring.factories") 
        : ClassLoader.getSystemResources("META-INF/spring.factories");
    LinkedMultiValueMap<String, String> result = new LinkedMultiValueMap<>();
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        UrlResource resource = new UrlResource(url);
        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
        for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryClassName = ((String) entry.getKey()).trim();
            for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                result.add(factoryClassName, factoryName.trim());
            }
        }
    }
    return result;
}

Filtering Candidate Configurations

The filter method utilizes implementations of AutoConfigurationImportFilter (like OnClassCondition, OnBeanCondition) to evaluate conditional annotations.

private List<String> filter(List<String> configurations, AutoConfigurationMetadata metadata) {
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        invokeAwareMethods(filter);
        boolean[] match = filter.match(candidates, metadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) { return 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;
}

Conditional Annotations

Spring 4 introduced @Conditional to register beans only when specific criteria are met. Spring Boot provides several specialized variants:

  • @ConditionalOnBean: Registers the bean only if a specific bean already exists in the context.
  • @ConditionalOnClass: Registers the bean only if a specific class is present on the classpath.
  • @ConditionalOnMissingBean: Registers the bean only if a specific bean does not exist in the context.
  • @ConditionalOnMissingClass: Registers the bean only if a specific class is absent from the classpath.
  • @ConditionalOnWebApplication: Registers the bean only if the application is a web application.
  • @ConditionalOnNotWebApplication: Registers the bean only if the application is not a web application.
  • @ConditionalOnProperty: Registers the bean based on the value of a specific configuration property.
  • @ConditionalOnExpression: Registers the bean based on the result of a SpEL expression.
  • @ConditionalOnJava: Registers the bean based on the JVM version.
  • @ConditionalOnResource: Registers the bean only if a specific resource exists on the classpath.
  • @ConditionalOnSingleCandidate: Registers the bean only if a specific bean has exactly one instance or multiple instances with a primary candidate.

Selecting and Sorting Imports

After processing, the selectImports method finalizes the list, removes exclusions, and sorts the configurations based on @AutoConfigureOrder or @Order annotations.

public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    }
    Set<String> allExclusions = this.autoConfigurationEntries.stream()
        .map(AutoConfigurationEntry::getExclusions)
        .flatMap(Collection::stream).collect(Collectors.toSet());
    Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
        .map(AutoConfigurationEntry::getConfigurations)
        .flatMap(Collection::stream)
        .collect(Collectors.toCollection(LinkedHashSet::new));
    processedConfigurations.removeAll(allExclusions);
    return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata())
        .stream()
        .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
        .collect(Collectors.toList());
}

The Auto-Configuration Pattern: xxxAutoConfiguration and xxxProperties

Auto-configuration classes typically bind external configuration properties to Java objects. Consider the following EncodingAutoConfig example:

@Configuration
@EnableConfigurationProperties(EncodingProperties.class)
@ConditionalOnWebApplication
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "app.encoding", value = "enabled", matchIfMissing = true)
public class EncodingAutoConfig {
    private final EncodingProperties props;
    public EncodingAutoConfig(EncodingProperties props) {
        this.props = props;
    }
    @Bean
    @ConditionalOnMissingBean
    public CharacterEncodingFilter encodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.props.getCharset().name());
        filter.setForceRequestEncoding(this.props.shouldForce(Type.REQUEST));
        filter.setForceResponseEncoding(this.props.shouldForce(Type.RESPONSE));
        return filter;
    }
}

The corresponding properties class maps directly to application properties:

@ConfigurationProperties(prefix = "app.encoding")
public class EncodingProperties {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    // getters and setters omitted
}

This establishes a clear pattern: xxxAutoConfiguration handles component registration and conditional logic, while xxxProperties extracts configurable attributes from the application.properties or application.yml file.

@ComponentScan Behavior

The @ComponentScan annotation within @SpringBootApplication dictates how Spring discovers annotated components. Without explicit basePackages definitions, it defaults to scanning the package of the main application class and its sub-packages. It configures custom exclude filters to ignore TypeExcludeFilter and AutoConfigurationExcludeFilter, ensuring auto-configuration classes are handled appropriately rather than being picked up as standard component scans.

Tags: Spring Boot auto-configuration java Spring Framework IoC Container

Posted on Tue, 19 May 2026 20:40:01 +0000 by lhcpr