Spring Boot 自动配置:@Conditional 与 spring.factories

Spring Boot 最引人注目的特性之一就是其**自动配置(Auto-Configuration)**机制。它让开发者能够在几乎零配置的情况下快速启动一个功能完善的 Spring 应用。本文将深入剖析自动配置的底层原理,重点讲解 @Conditional 条件注解和 spring.factories 加载机制,并通过实际案例展示如何开发自定义 Starter。

一、自动配置的核心思想

1.1 约定优于配置

Spring Boot 的设计理念是**"约定优于配置"(Convention Over Configuration)**。传统 Spring 应用需要大量的 XML 或 Java 配置来定义 Bean,而 Spring Boot 通过智能推断,自动为你配置好最常用的组件。

// 传统 Spring 需要这样配置 DataSource
@Bean
public DataSource dataSource() {
    BasicDataSource ds = new BasicDataSource();
    ds.setUrl("jdbc:mysql://localhost:3306/mydb");
    ds.setUsername("root");
    ds.setPassword("password");
    return ds;
}

// Spring Boot 只需要在 application.yml 中配置
// spring.datasource.url=jdbc:mysql://localhost:3306/mydb
// 自动配置会根据 classpath 中的依赖自动创建 DataSource

1.2 自动配置的触发条件

Spring Boot 的自动配置并非无条件生效,它基于以下因素进行智能判断:

  • Classpath 扫描:检测类路径中是否存在特定类
  • 属性配置:检查 application.propertiesapplication.yml 中的配置项
  • 环境条件:判断当前运行环境(Servlet/Reactive、JDK 版本等)
  • 自定义条件:通过 @Conditional 注解定义复杂条件

二、@Conditional 条件注解详解

@Conditional 是 Spring 4.0 引入的注解,用于根据特定条件决定是否创建 Bean 或加载配置类。Spring Boot 在此基础上扩展了大量实用的条件注解。

2.1 常用条件注解一览

注解作用示例场景
@ConditionalOnClass类路径存在指定类时生效检测到 MySQL 驱动时配置数据源
@ConditionalOnMissingClass类路径不存在指定类时生效无 Redis 时配置本地缓存
@ConditionalOnBean容器中存在指定 Bean 时生效有 DataSource 时配置 JdbcTemplate
@ConditionalOnMissingBean容器中不存在指定 Bean 时生效用户未自定义时提供默认实现
@ConditionalOnProperty指定属性满足条件时生效开关控制功能启用/禁用
@ConditionalOnWebApplication是 Web 应用时生效Web 环境特有的配置
@ConditionalOnExpressionSpEL 表达式为 true 时生效复杂逻辑判断

2.2 条件注解实战示例

示例 1:根据类路径自动配置

@Configuration
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public class MySQLAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean  // 用户已自定义时不覆盖
    public DataSource mysqlDataSource(DataSourceProperties properties) {
        return DataSourceBuilder.create()
            .url(properties.getUrl())
            .username(properties.getUsername())
            .password(properties.getPassword())
            .driverClassName("com.mysql.cj.jdbc.Driver")
            .build();
    }
}

示例 2:根据属性开关控制

@Configuration
@ConditionalOnProperty(
    prefix = "app.cache",  // 前缀
    name = "enabled",      // 属性名
    havingValue = "true",  // 期望值
    matchIfMissing = true  // 属性不存在时默认启用
)
public class CacheAutoConfiguration {
    
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }
}

对应的 application.yml

app:
  cache:
    enabled: true
    ttl: 3600

示例 3:组合条件判断

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(RedisOperations.class)
@ConditionalOnProperty(prefix = "spring.redis", name = "host")
public class RedisSessionAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(name = "springSessionDefaultRedisSerializer")
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

2.3 自定义条件注解

当内置条件无法满足需求时,可以实现 Condition 接口创建自定义条件。

// 自定义条件:检查是否在云环境中运行
public class OnCloudEnvironmentCondition implements Condition {
    
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 检查环境变量或系统属性
        String cloudProvider = System.getenv("CLOUD_PROVIDER");
        return StringUtils.hasText(cloudProvider);
    }
}

// 创建组合注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnCloudEnvironmentCondition.class)
public @interface ConditionalOnCloud {
    String value() default ""; // 可指定特定云厂商
}

// 使用自定义条件
@Configuration
@ConditionalOnCloud("aws")
public class AWSAutoConfiguration {
    // AWS 特有的配置
}

三、spring.factories 加载机制

spring.factories 是 Spring Boot 自动配置的核心入口,它定义了所有需要自动配置的类。

3.1 文件位置与格式

spring.factories 文件位于 META-INF/ 目录下,采用 Properties 格式:

# META-INF/spring.factories
# 自动配置类列表(Spring Boot 2.7+ 推荐使用新的 imports 文件)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.starter.MyAutoConfiguration,\
com.example.starter.AnotherAutoConfiguration

# 其他 SPI 扩展点
org.springframework.context.ApplicationContextInitializer=\
com.example.starter.MyContextInitializer

org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.starter.MyEnvironmentPostProcessor

3.2 加载原理

Spring Boot 启动时会调用 SpringFactoriesLoader.loadFactories() 方法:

// SpringApplication.run() 内部调用
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
    EnableAutoConfiguration.class,
    getClass().getClassLoader()
);

加载流程:

  1. 扫描所有 jar 包:查找 META-INF/spring.factories 文件
  2. 解析配置项:读取 EnableAutoConfiguration 键对应的类名列表
  3. 去重与排序:根据 @AutoConfigureOrder@AutoConfigureAfter 排序
  4. 条件过滤:使用 @Conditional 注解过滤不满足条件的配置类
  5. 加载配置:将满足条件的配置类注册到 Spring 容器

3.3 Spring Boot 2.7+ 的变化

Spring Boot 2.7 引入了新的加载方式,推荐在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中列出自动配置类:

# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.starter.MyAutoConfiguration
com.example.starter.AnotherAutoConfiguration

新方式的优点:

  • 文件内容更清晰,每行一个类名
  • 避免 Properties 文件的转义问题
  • 更好的 IDE 支持

四、开发自定义 Starter

4.1 Starter 项目结构

my-spring-boot-starter/
├── pom.xml
└── src/
    └── main/
        ├── java/
        │   └── com/example/starter/
        │       ├── MyAutoConfiguration.java      # 自动配置类
        │       ├── MyProperties.java              # 配置属性类
        │       ├── MyService.java                 # 核心服务类
        │       └── MyServiceBuilder.java          # 构建器
        └── resources/
            └── META-INF/
                ├── spring.factories               # 旧版配置
                └── spring/
                    └── org.springframework.boot.autoconfigure.AutoConfiguration.imports  # 新版配置

4.2 完整 Starter 开发示例

步骤 1:定义配置属性类

@ConfigurationProperties(prefix = "my.starter")
public class MyProperties {
    
    private boolean enabled = true;
    private String endpoint = "https://api.example.com";
    private int timeout = 5000;
    private Map<String, String> headers = new HashMap<>();
    
    // getters and setters
}

步骤 2:创建核心服务

public class MyService {
    
    private final String endpoint;
    private final int timeout;
    
    public MyService(String endpoint, int timeout) {
        this.endpoint = endpoint;
        this.timeout = timeout;
    }
    
    public String execute(String request) {
        // 执行业务逻辑
        return "Result of " + request;
    }
}

步骤 3:编写自动配置类

@Configuration
@EnableConfigurationProperties(MyProperties.class)
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "my.starter", name = "enabled", matchIfMissing = true)
public class MyAutoConfiguration {
    
    private final MyProperties properties;
    
    public MyAutoConfiguration(MyProperties properties) {
        this.properties = properties;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService(
            properties.getEndpoint(),
            properties.getTimeout()
        );
    }
    
    @Bean
    @ConditionalOnBean(MyService.class)
    public MyServiceHealthIndicator myHealthIndicator(MyService service) {
        return new MyServiceHealthIndicator(service);
    }
}

步骤 4:注册自动配置

创建 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.example.starter.MyAutoConfiguration

步骤 5:添加 additional-spring-configuration-metadata.json

为了提供更好的 IDE 自动提示,添加配置元数据:

{
  "groups": [
    {
      "name": "my.starter",
      "type": "com.example.starter.MyProperties",
      "sourceType": "com.example.starter.MyProperties"
    }
  ],
  "properties": [
    {
      "name": "my.starter.enabled",
      "type": "java.lang.Boolean",
      "description": "是否启用 My Starter",
      "defaultValue": true
    },
    {
      "name": "my.starter.endpoint",
      "type": "java.lang.String",
      "description": "API 端点地址"
    },
    {
      "name": "my.starter.timeout",
      "type": "java.lang.Integer",
      "description": "请求超时时间(毫秒)",
      "defaultValue": 5000
    }
  ]
}

4.3 使用自定义 Starter

在其他项目中引入:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

配置使用:

my:
  starter:
    endpoint: https://custom-api.example.com
    timeout: 10000

注入使用:

@Service
public class BusinessService {
    
    @Autowired
    private MyService myService;
    
    public void doSomething() {
        String result = myService.execute("request");
        // ...
    }
}

五、自动配置排错与调试

5.1 查看自动配置报告

启动时添加 --debug 参数或在配置中启用:

debug: true

控制台将输出详细的自动配置报告:

Positive matches:(生效的配置)
-----------------
   MyAutoConfiguration matched:
      - @ConditionalOnClass found required class 'com.example.MyService' (OnClassCondition)
      - @ConditionalOnProperty (my.starter.enabled=true) matched (OnPropertyCondition)

Negative matches:(未生效的配置)
-----------------
   RedisAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.springframework.data.redis.core.RedisOperations' (OnClassCondition)

5.2 排除特定自动配置

@SpringBootApplication(
    exclude = {
        DataSourceAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class
    }
)
public class Application {
    // ...
}

或使用配置:

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

六、最佳实践总结

  1. 优先使用 @ConditionalOnMissingBean:允许用户覆盖默认配置,提供灵活性
  2. 合理设计配置前缀:使用有意义的命名空间,如 mycompany.feature
  3. 提供 sensible defaults:合理的默认值减少用户配置负担
  4. 添加配置元数据:帮助 IDE 提供自动提示
  5. 编写条件注解的合理组合:避免配置冲突,确保条件互斥
  6. 版本兼容性考虑:使用 @AutoConfigureAfter 控制配置加载顺序
  7. 文档先行:清晰说明 Starter 的功能、配置项和使用方式

结语

Spring Boot 的自动配置机制是其生态系统成功的关键。通过 @Conditional 条件注解和 spring.factories 加载机制,开发者可以构建高度可插拔、零配置的组件。掌握这些原理不仅能帮助你更好地使用 Spring Boot,还能让你开发出高质量的自定义 Starter,为团队和社区贡献价值。

理解自动配置的底层原理,是成为 Spring Boot 高级开发者的必经之路。希望本文能帮助你深入理解这一核心机制。