Backend Development 17 min read

Understanding Spring Boot Auto‑Configuration and the @Conditional Mechanism

This article explains Spring Boot's core features, the role of @Conditional in automatic configuration, demonstrates custom condition classes and annotations, and walks through the source code of Spring Boot's auto‑configuration infrastructure, including @SpringBootApplication, EnableAutoConfigurationImportSelector, and class‑path condition evaluation.

Java Captain
Java Captain
Java Captain
Understanding Spring Boot Auto‑Configuration and the @Conditional Mechanism

Spring Boot is a sub‑project of Spring that follows the “convention over configuration” principle, providing embedded servlet containers, starter dependencies, automatic configuration without XML, and actuator monitoring.

The article then explains that Spring Cloud builds on Spring Boot and that understanding Spring Boot’s auto‑configuration, which relies on the @Conditional annotation, is useful for micro‑service development.

It introduces @Conditional, shows how @Profile can be used for simple environment‑based bean registration, and provides three ways to set active profiles (programmatically, JVM property, servlet init‑param).

Custom condition classes are demonstrated: a ProfileCondition example, a database‑type condition that checks a system property, and examples that check classpath presence, bean registration, or configuration properties. Code snippets illustrate these implementations.

@Configuration
public class AppConfig {
    @Bean
    @Profile("DEV")
    public DataSource devDataSource() {
        ...
    }

    @Bean
    @Profile("PROD")
    public DataSource prodDataSource() {
        ...
    }
}

Further, a custom annotation @DatabaseType is defined that meta‑annotates @Conditional with a DatabaseTypeCondition class, which reads a system property to decide which bean to create. Example configuration class shows beans annotated with @DatabaseType.

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DatabaseTypeCondition.class)
public @interface DatabaseType {
    String value();
}
public class DatabaseTypeCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map
attrs = metadata.getAnnotationAttributes(DatabaseType.class.getName());
        String type = (String) attrs.get("value");
        String enabledDBType = System.getProperty("dbType", "MySql");
        return enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type);
    }
}

The article then dives into the source of Spring Boot’s auto‑configuration. It analyses @SpringBootApplication (a composition of @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan) and the EnableAutoConfigurationImportSelector class, which delegates to AutoConfigurationImportSelector.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    Class
[] exclude() default {};
    String[] excludeName() default {};
}
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
    protected boolean isEnabled(AnnotationMetadata metadata) {
        return this.getClass().equals(EnableAutoConfigurationImportSelector.class)
            ? ((Boolean) this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, Boolean.TRUE)).booleanValue()
            : true;
    }
}

AutoConfigurationImportSelector loads candidate configuration class names from META‑INF/spring.factories via SpringFactoriesLoader, removes duplicates, sorts them, applies exclusions, and returns the final list.

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        try {
            AutoConfigurationMetadata ex = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            configurations = this.sort(configurations, ex);
            Set exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, ex);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return configurations.toArray(new String[configurations.size()]);
        } catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }
}
public static List
loadFactoryNames(Class
factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
        Enumeration
resources = (classLoader != null) ?
            classLoader.getResources("META-INF/spring.factories") :
            ClassLoader.getSystemResources("META-INF/spring.factories");
        List
result = new ArrayList<>();
        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String names = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(names)));
        }
        return result;
    } catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [META-INF/spring.factories]", ex);
    }
}

The source of @ConditionalOnClass is examined; it is a meta‑annotation that uses OnClassCondition, which extends SpringBootCondition. The condition checks that required classes are present on the classpath, otherwise the auto‑configuration class is not loaded. The implementation uses a MatchType enum (PRESENT, MISSING) and class‑loader checks.

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    ClassLoader classLoader = context.getClassLoader();
    List
onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
    if (onClasses != null) {
        List
missing = this.getMatches(onClasses, MatchType.MISSING, classLoader);
        if (!missing.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                .didNotFind("required class", "required classes").items(Style.QUOTE, missing));
        }
        // record present classes
    }
    // similar logic for @ConditionalOnMissingClass
    return ConditionOutcome.match(matchMessage);
}
private enum MatchType {
    PRESENT {
        public boolean matches(String className, ClassLoader classLoader) {
            return isPresent(className, classLoader);
        }
    },
    MISSING {
        public boolean matches(String className, ClassLoader classLoader) {
            return !isPresent(className, classLoader);
        }
    };
    private static boolean isPresent(String className, ClassLoader classLoader) {
        if (classLoader == null) {
            classLoader = ClassUtils.getDefaultClassLoader();
        }
        try {
            forName(className, classLoader);
            return true;
        } catch (Throwable ex) {
            return false;
        }
    }
    private static Class
forName(String className, ClassLoader classLoader) throws ClassNotFoundException {
        return (classLoader != null) ? classLoader.loadClass(className) : Class.forName(className);
    }
    public abstract boolean matches(String className, ClassLoader classLoader);
}

Finally, the article notes that many similar conditional annotations exist (e.g., @ConditionalOnBean, @ConditionalOnWebApplication) and that developers can create their own starters by defining custom conditions.

Javabackend developmentSpring BootAuto‑Configurationconditional
Java Captain
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.