02.01 Spring Boot|自动配置机制深度解析

02.01 Spring Boot|自动配置机制深度解析

导读

  • 目标:深入理解Spring Boot的自动配置机制,包括@EnableAutoConfiguration、条件注解、spring.factories等核心机制,掌握自动配置的设计思想和实现原理。
  • 适用场景:Spring Boot学习、自定义自动配置、框架扩展、面试准备。
  • 前置知识:Spring IoC容器、条件注解、@Configuration

一、自动配置核心概念

1.1 什么是自动配置?

**自动配置(Auto Configuration)**是Spring Boot的核心特性之一,它能够根据类路径中的依赖和配置,自动配置Spring应用所需的Bean。

传统Spring配置 vs Spring Boot自动配置

java 复制代码
// 传统Spring配置(繁琐)
@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        config.setUsername("root");
        config.setPassword("root");
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return new HikariDataSource(config);
    }
}

// Spring Boot自动配置(简单)
// 只需在application.yml中配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
// Spring Boot会自动创建DataSource Bean

自动配置的优势

  1. 零配置:开箱即用,减少配置代码
  2. 约定优于配置:遵循最佳实践
  3. 可覆盖:可以自定义配置覆盖默认配置
  4. 条件化:根据条件自动启用或禁用

1.2 @SpringBootApplication注解解析

@SpringBootApplication组成

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration  // = @Configuration
@EnableAutoConfiguration   // 启用自动配置(核心)
@ComponentScan             // 组件扫描
public @interface SpringBootApplication {
    // 排除自动配置类
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
    
    // 组件扫描配置
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
}

@SpringBootApplication = 三个注解的组合

java 复制代码
// 等价于
@Configuration              // 配置类
@EnableAutoConfiguration    // 启用自动配置
@ComponentScan              // 组件扫描
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

1.3 @EnableAutoConfiguration核心机制

@EnableAutoConfiguration注解

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)  // 核心:导入选择器
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
    // 排除自动配置类
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

AutoConfigurationImportSelector工作原理

java 复制代码
public class AutoConfigurationImportSelector implements 
        DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware {
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 1. 检查是否启用自动配置
        if (!isEnabled(annotationMetadata)) {
            return new String[0];
        }
        
        // 2. 获取自动配置类列表
        AutoConfigurationEntry autoConfigurationEntry = 
            getAutoConfigurationEntry(annotationMetadata);
        
        // 3. 返回自动配置类全限定名数组
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
    protected AutoConfigurationEntry getAutoConfigurationEntry(
            AnnotationMetadata annotationMetadata) {
        // 1. 获取所有自动配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        
        // 2. 去重
        configurations = removeDuplicates(configurations);
        
        // 3. 排除指定的自动配置类
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        configurations.removeAll(exclusions);
        
        // 4. 过滤(条件注解检查)
        configurations = getConfigurationClassFilter().filter(configurations);
        
        // 5. 触发自动配置导入事件
        fireAutoConfigurationImportEvents(configurations, exclusions);
        
        return new AutoConfigurationEntry(configurations, exclusions);
    }
    
    protected List<String> getCandidateConfigurations(
            AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // 从META-INF/spring.factories加载自动配置类
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
            getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        
        return configurations;
    }
}

关键点

  1. 扫描spring.factories :从META-INF/spring.factories文件加载自动配置类
  2. 条件过滤:使用条件注解过滤不满足条件的配置类
  3. 去重和排除:处理重复和排除的配置类

二、spring.factories机制

2.1 spring.factories文件格式

文件位置META-INF/spring.factories

文件格式

properties 复制代码
# 自动配置类列表
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.DataSourceAutoConfiguration,\
com.example.autoconfigure.RedisAutoConfiguration,\
com.example.autoconfigure.KafkaAutoConfiguration

# 其他工厂类
org.springframework.boot.ApplicationContextInitializer=\
com.example.initializer.MyInitializer

org.springframework.boot.ApplicationListener=\
com.example.listener.MyListener

Spring Boot内置自动配置类示例

properties 复制代码
# spring-boot-autoconfigure-2.7.0.jar/META-INF/spring.factories
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.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jpa.JpaBaseConfiguration,\
org.springframework.boot.autoconfigure.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transport.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.transport.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

2.2 SpringFactoriesLoader加载机制

SpringFactoriesLoader源码分析

java 复制代码
public final class SpringFactoriesLoader {
    
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    // 缓存已加载的工厂类
    private static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
    
    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        // 1. 获取工厂类名称列表
        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoader);
        
        // 2. 实例化工厂类
        List<T> result = new ArrayList<>(factoryImplementationNames.size());
        for (String factoryImplementationName : factoryImplementationNames) {
            result.add(instantiateFactory(factoryImplementationName, factoryType, classLoader));
        }
        
        // 3. 排序(实现Ordered接口或@Order注解)
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
    
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        // 1. 从缓存获取
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
        
        // 2. 加载所有spring.factories文件
        result = new HashMap<>();
        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                
                // 3. 解析properties文件
                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, k -> new ArrayList<>())
                              .add(factoryImplementationName.trim());
                    }
                }
            }
            
            // 4. 去重并排序
            result.replaceAll((k, v) -> v.stream().distinct().collect(Collectors.toList()));
            cache.put(classLoader, result);
        } catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }
}

关键点

  1. 多文件支持:可以加载多个jar包中的spring.factories文件
  2. 缓存机制:使用ConcurrentReferenceHashMap缓存,提高性能
  3. 排序支持:支持Ordered接口和@Order注解排序
  4. 去重处理:自动处理重复的配置类

三、条件注解详解

3.1 条件注解概述

**条件注解(Conditional Annotations)**是Spring Boot自动配置的核心机制,用于控制配置类或Bean的创建条件。

常用条件注解

注解 说明 示例
@ConditionalOnClass 类路径存在指定类时生效 @ConditionalOnClass(DataSource.class)
@ConditionalOnMissingClass 类路径不存在指定类时生效 @ConditionalOnMissingClass("com.example.Class")
@ConditionalOnBean 容器中存在指定Bean时生效 @ConditionalOnBean(DataSource.class)
@ConditionalOnMissingBean 容器中不存在指定Bean时生效 @ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty 配置属性满足条件时生效 @ConditionalOnProperty(name="enabled", havingValue="true")
@ConditionalOnResource 资源文件存在时生效 @ConditionalOnResource(resources="classpath:config.xml")
@ConditionalOnWebApplication Web应用时生效 @ConditionalOnWebApplication
@ConditionalOnNotWebApplication 非Web应用时生效 @ConditionalOnNotWebApplication
@ConditionalOnExpression SpEL表达式为true时生效 @ConditionalOnExpression("${feature.enabled:true}")

3.2 条件注解源码分析

@ConditionalOnClass实现

java 复制代码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)  // 条件判断类
public @interface ConditionalOnClass {
    Class<?>[] value() default {};
    String[] name() default {};
}

// OnClassCondition实现
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
    
    @Override
    protected final ConditionOutcome[] getOutcomes(
            AnnotatedTypeMetadata metadata, ConditionContext context) {
        
        // 获取@ConditionalOnClass注解
        MultiValueMap<String, Object> onClasses = 
            getAttributesForAll(metadata, ConditionalOnClass.class);
        
        List<String> missingClasses = new ArrayList<>();
        for (Object value : onClasses.values()) {
            if (value instanceof String) {
                if (!ClassUtils.isPresent((String) value, context.getClassLoader())) {
                    missingClasses.add((String) value);
                }
            } else if (value instanceof Class) {
                if (!ClassUtils.isPresent(((Class<?>) value).getName(), context.getClassLoader())) {
                    missingClasses.add(((Class<?>) value).getName());
                }
            }
        }
        
        if (!missingClasses.isEmpty()) {
            return new ConditionOutcome[] { 
                ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                    .didNotFind("required class", "required classes").items(Style.QUOTE, missingClasses))
            };
        }
        
        return new ConditionOutcome[] { ConditionOutcome.match() };
    }
}

@ConditionalOnProperty实现

java 复制代码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    String[] value() default {};
    String prefix() default "";
    String[] name() default {};
    String havingValue() default "";
    boolean matchIfMissing() default false;
}

// OnPropertyCondition实现
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
    
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, 
            AnnotatedTypeMetadata metadata) {
        
        List<AnnotationAttributes> allAnnotationAttributes = 
            annotationAttributesFromMultiValueMap(
                metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
        
        List<ConditionMessage> noMatch = new ArrayList<>();
        List<ConditionMessage> match = new ArrayList<>();
        
        for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
            ConditionOutcome outcome = determineOutcome(annotationAttributes, 
                context.getEnvironment());
            (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
        }
        
        if (!noMatch.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
        }
        return ConditionOutcome.match(ConditionMessage.of(match));
    }
    
    private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, 
            Environment environment) {
        Spec spec = new Spec(annotationAttributes);
        List<String> missingProperties = new ArrayList<>();
        List<String> nonMatchingProperties = new ArrayList<>();
        
        spec.collectProperties(environment, missingProperties, nonMatchingProperties);
        
        if (!nonMatchingProperties.isEmpty()) {
            return ConditionOutcome.noMatch(
                ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
                    .found("different value in property", "different values in properties")
                    .items(Style.QUOTE, nonMatchingProperties));
        }
        
        if (!missingProperties.isEmpty()) {
            return ConditionOutcome.noMatch(
                ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
                    .didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
        }
        
        return ConditionOutcome.match(
            ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
    }
}

3.3 条件注解实战案例

案例1:DataSource自动配置

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "javax.sql.DataSource")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
        DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
    
    @Configuration(proxyBeanMethods = false)
    @Conditional(EmbeddedDatabaseCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ EmbeddedDataSourceConfiguration.class,
            EmbeddedDataSourceJmxConfiguration.class })
    protected static class EmbeddedDatabaseConfiguration {
    }
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(HikariDataSource.class)
    @ConditionalOnMissingBean(DataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.type", 
                          havingValue = "com.zaxxer.hikari.HikariDataSource",
                          matchIfMissing = true)
    static class Hikari {
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.hikari")
        HikariDataSource dataSource(DataSourceProperties properties) {
            HikariDataSource dataSource = createDataSource(properties, 
                HikariDataSource.class);
            if (StringUtils.hasText(properties.getName())) {
                dataSource.setPoolName(properties.getName());
            }
            return dataSource;
        }
    }
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
    @ConditionalOnMissingBean(DataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.type", 
                          havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
                          matchIfMissing = false)
    static class Tomcat {
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.tomcat")
        org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
            return createDataSource(properties, org.apache.tomcat.jdbc.pool.DataSource.class);
        }
    }
}

条件注解执行顺序

  1. @ConditionalOnClass:检查类路径是否存在DataSource类
  2. @ConditionalOnMissingBean:检查容器中是否已存在DataSource Bean
  3. @ConditionalOnProperty:检查配置属性是否满足条件
  4. 如果所有条件都满足,才创建DataSource Bean

案例2:Redis自动配置

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

四、自动配置实战案例

4.1 自定义自动配置类

完整示例:短信服务自动配置

java 复制代码
// 1. 配置属性类
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
    private String accessKeyId;
    private String accessKeySecret;
    private String signName;
    private String templateCode;
    private boolean enabled = true;
    
    // getters and setters
    public String getAccessKeyId() { return accessKeyId; }
    public void setAccessKeyId(String accessKeyId) { this.accessKeyId = accessKeyId; }
    // ... 其他getter/setter
}

// 2. 服务接口
public interface SmsService {
    void sendSms(String phone, String message);
}

// 3. 服务实现
public class AliyunSmsService implements SmsService {
    private final SmsProperties properties;
    
    public AliyunSmsService(SmsProperties properties) {
        this.properties = properties;
    }
    
    @Override
    public void sendSms(String phone, String message) {
        // 阿里云短信发送逻辑
        System.out.println("发送短信到: " + phone + ", 内容: " + message);
    }
}

// 4. 自动配置类
@Configuration
@ConditionalOnClass(SmsService.class)
@ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(SmsProperties.class)
public class SmsAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(SmsService.class)
    public SmsService smsService(SmsProperties properties) {
        return new AliyunSmsService(properties);
    }
}

// 5. spring.factories
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.SmsAutoConfiguration

// 6. 配置文件
# application.yml
sms:
  enabled: true
  access-key-id: your-access-key-id
  access-key-secret: your-access-key-secret
  sign-name: 你的签名
  template-code: SMS_123456789

// 7. 使用
@Service
public class UserService {
    @Autowired
    private SmsService smsService;  // 自动注入
    
    public void sendVerificationCode(String phone) {
        smsService.sendSms(phone, "您的验证码是:123456");
    }
}

4.2 自动配置最佳实践

1. 使用条件注解控制配置

java 复制代码
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = "my.datasource", name = "enabled", havingValue = "true")
public class MyDataSourceAutoConfiguration {
    // 只在满足条件时创建
}

2. 提供默认配置

java 复制代码
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public MyService myService(MyProperties properties) {
        // 提供默认实现
        MyProperties defaultProps = new MyProperties();
        defaultProps.setTimeout(1000);
        defaultProps.setRetries(3);
        
        // 合并用户配置
        if (properties.getTimeout() != null) {
            defaultProps.setTimeout(properties.getTimeout());
        }
        
        return new MyService(defaultProps);
    }
}

3. 支持多实现选择

java 复制代码
@Configuration
@ConditionalOnClass(MyService.class)
public class MyAutoConfiguration {
    
    @Bean
    @ConditionalOnProperty(prefix = "my.service", name = "type", havingValue = "aliyun")
    @ConditionalOnMissingBean
    public MyService aliyunService(MyProperties properties) {
        return new AliyunService(properties);
    }
    
    @Bean
    @ConditionalOnProperty(prefix = "my.service", name = "type", havingValue = "tencent")
    @ConditionalOnMissingBean
    public MyService tencentService(MyProperties properties) {
        return new TencentService(properties);
    }
}

4. 提供配置元数据

json 复制代码
// META-INF/spring-configuration-metadata.json
{
  "properties": [
    {
      "name": "sms.enabled",
      "type": "java.lang.Boolean",
      "defaultValue": true,
      "description": "是否启用短信服务"
    },
    {
      "name": "sms.access-key-id",
      "type": "java.lang.String",
      "description": "短信服务AccessKeyId"
    }
  ]
}

五、禁用和排除自动配置

5.1 禁用自动配置

方式1:使用@SpringBootApplication的exclude属性

java 复制代码
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    JpaRepositoriesAutoConfiguration.class
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

方式2:使用配置文件

yaml 复制代码
# application.yml
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

方式3:使用条件属性

yaml 复制代码
# application.yml
spring:
  datasource:
    type: none  # 禁用DataSource自动配置

5.2 条件排除

使用@ConditionalOnProperty控制

java 复制代码
@Configuration
@ConditionalOnProperty(
    prefix = "feature",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = false  // 默认不启用
)
public class FeatureAutoConfiguration {
    // 只有配置feature.enabled=true时才启用
}

高频面试问答(深度解析)

1. Spring Boot自动配置原理?

标准答案

  1. @EnableAutoConfiguration:启用自动配置
  2. AutoConfigurationImportSelector:选择自动配置类
  3. spring.factories:定义自动配置类列表
  4. 条件注解:控制自动配置生效条件

深入追问

Q: 自动配置的执行顺序?

java 复制代码
// 1. 扫描所有META-INF/spring.factories文件
// 2. 加载EnableAutoConfiguration对应的配置类列表
// 3. 过滤(排除指定的配置类)
// 4. 条件注解检查(@ConditionalOnClass等)
// 5. 去重和排序
// 6. 导入配置类到Spring容器

Q: 如何调试自动配置?

bash 复制代码
# 1. 启用调试日志
logging.level.org.springframework.boot.autoconfigure=DEBUG

# 2. 查看自动配置报告
# 访问 /actuator/conditions(需要Actuator)

# 3. 查看已启用的自动配置
# 访问 /actuator/configprops

2. 条件注解的执行时机?

标准答案

条件注解在Bean定义阶段执行,而不是Bean实例化阶段。

执行流程

复制代码
1. 加载配置类
2. 解析@Conditional注解
3. 执行Condition.matches()方法
4. 如果条件满足,注册Bean定义
5. 如果条件不满足,跳过Bean定义

延伸阅读

相关推荐
一起搞IT吧2 小时前
三方相机问题分析十一:【手电筒回调异常】手电筒打开3档时,达到档位控制温度,手电筒二级界面中档位为0
android·图像处理·数码相机
yuuki2332332 小时前
【C++】掌握list:C++链表容器的核心奥秘
c++·后端·链表·list
Coder_Boy_2 小时前
基于SpringAI的智能AIOps项目:部署相关容器化部署管理技术图解版
人工智能·spring boot·算法·贪心算法·aiops
wanghowie2 小时前
01.03 Spring核心|事务管理实战
java·后端·spring
千寻技术帮2 小时前
10356_基于Springboot的老年人管理系统
java·spring boot·后端·vue·老年人
最贪吃的虎2 小时前
Redis 除了缓存,还能干什么?
java·数据库·redis·后端·缓存
2501_924064112 小时前
2025年移动应用渗透测试流程方案及iOS安卓测试方法对比
android·ios
Qiuner2 小时前
Spring Boot 全局异常处理策略设计(一):异常不只是 try-catch
java·spring boot·后端
superman超哥2 小时前
Rust 错误处理模式:Result、?运算符与 anyhow 的最佳实践
开发语言·后端·rust·运算符·anyhow·rust 错误处理