文章 573
评论 5
浏览 219483
3个被 90% 程序员忽略的 Spring Boot 注解,用了少写一半代码

3个被 90% 程序员忽略的 Spring Boot 注解,用了少写一半代码

一、引言

Spring Boot 提供了数百个注解,但大多数开发者只使用了其中的 10%。今天我要分享的这 3 个注解,虽然不是最热门的,但用好了能让你的代码更简洁、更优雅、更易维护。

核心观点:好的代码不是写得更多,而是写得更少——用最简洁的方式表达最复杂的逻辑。

二、@ConfigurationProperties:比 @Value 强 10 倍的配置绑定

2.1 问题场景

你是否写过这样的代码?

@RestController
public class UserController {
    
    @Value("${app.user.default-page-size}")
    private int defaultPageSize;
    
    @Value("${app.user.max-page-size}")
    private int maxPageSize;
    
    @Value("${app.user.cache-enabled}")
    private boolean cacheEnabled;
    
    @Value("${app.user.cache-ttl-minutes}")
    private int cacheTtlMinutes;
    
    @Value("${app.user.api-timeout-seconds}")
    private int apiTimeoutSeconds;
    
    // ... 业务逻辑
}

这种方式有什么问题?

  1. 类型不安全:全部是字符串,需要手动转换
  2. 松散耦合:配置分散在各个类中,难以管理
  3. 缺少校验:没有默认值和范围校验
  4. 重复代码:每个字段都要写 @Value

2.2 解决方案:@ConfigurationProperties

@Data
@ConfigurationProperties(prefix = "app.user")
public class UserProperties {
    
    private int defaultPageSize = 20;
    
    @Min(1)
    @Max(100)
    private int maxPageSize = 100;
    
    private boolean cacheEnabled = true;
    
    @Positive
    private int cacheTtlMinutes = 30;
    
    @Positive
    private int apiTimeoutSeconds = 5;
}

启用配置类:

@Configuration
@EnableConfigurationProperties(UserProperties.class)
public class AppConfig {
}

使用方式:

@RestController
public class UserController {
    
    private final UserProperties properties;
    
    public UserController(UserProperties properties) {
        this.properties = properties;
    }
    
    @GetMapping("/users")
    public ResponseEntity<?> getUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        
        int actualSize = Math.min(size, properties.getMaxPageSize());
        // ...
    }
}

2.3 核心优势对比

特性@Value@ConfigurationProperties
类型安全❌ 字符串为主✅ 完整类型支持
默认值需手动处理✅ 字段默认值
宽松绑定✅ kebab-case/camelCase
JSR-303 校验✅ @Validated
配置分组✅ 按前缀分组
IDE 提示✅ 完整提示
文档生成✅ spring-boot-configuration-processor

2.4 宽松绑定示例

# application.yml
app:
  user:
    default-page-size: 20    # kebab-case
    maxPageSize: 100         # camelCase
    max_page_size: 100       # underscore_case

以上三种写法都能正确绑定到 maxPageSize 字段。

2.5 校验示例

@Data
@Validated
@ConfigurationProperties(prefix = "app.user")
public class UserProperties {
    
    @NotNull
    @Size(min = 3, max = 50)
    private String defaultRole;
    
    @Min(1)
    @Max(100)
    private int maxPageSize;
    
    @Email
    private String supportEmail;
    
    @Pattern(regexp = "^[a-z]+$")
    private String environment;
}

启动时如果配置不符合要求,会抛出 BindValidationException

三、@ConditionalOnExpression:用 SpEL 写复杂条件判断

3.1 问题场景

你是否遇到过这种情况?

@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConditionalOnProperty(name = "app.datasource.type", havingValue = "mysql")
    public DataSource mysqlDataSource() {
        // MySQL 配置
    }
    
    @Bean
    @ConditionalOnProperty(name = "app.datasource.type", havingValue = "postgresql")
    public DataSource postgresqlDataSource() {
        // PostgreSQL 配置
    }
    
    // 如果需要同时满足多个条件?
    // 如果需要否定条件?
    // 如果需要表达式判断?
}

@ConditionalOnProperty 只能处理简单的等值判断,遇到复杂场景就力不从心了。

3.2 解决方案:@ConditionalOnExpression

@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConditionalOnExpression("'${app.datasource.type}' == 'mysql' && ${app.datasource.enabled:true}")
    public DataSource mysqlDataSource() {
        return new MySQLDataSource();
    }
    
    @Bean
    @ConditionalOnExpression("'${app.datasource.type}' == 'postgresql' && ${app.datasource.enabled:true}")
    public DataSource postgresqlDataSource() {
        return new PostgreSQLDataSource();
    }
    
    @Bean
    @ConditionalOnExpression("${app.datasource.type} != 'mysql' && ${app.datasource.type} != 'postgresql'")
    public DataSource defaultDataSource() {
        return new H2DataSource();
    }
    
    @Bean
    @ConditionalOnExpression("#{!environment.containsProperty('app.datasource.type')}")
    public DataSource autoDetectDataSource() {
        return new AutoDetectDataSource();
    }
}

3.3 SpEL 表达式技巧

字符串比较

@ConditionalOnExpression("'${app.env}' == 'production'")

数值比较

@ConditionalOnExpression("${app.threads:0} > 10")

否定条件

@ConditionalOnExpression("!${app.feature.enabled:false}")

多条件组合

@ConditionalOnExpression("${app.env} == 'test' || ${app.env} == 'dev'")

检查属性是否存在

@ConditionalOnExpression("#{environment.containsProperty('app.custom.config')}")

3.4 实际应用场景

场景一:根据环境加载不同配置

@Configuration
public class MonitoringConfig {
    
    @Bean
    @ConditionalOnExpression("${app.env} == 'production'")
    public PrometheusMeterRegistry prometheusMeterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
    
    @Bean
    @ConditionalOnExpression("${app.env} != 'production'")
    public SimpleMeterRegistry simpleMeterRegistry() {
        return new SimpleMeterRegistry();
    }
}

场景二:功能开关组合

@Configuration
public class FeatureConfig {
    
    @Bean
    @ConditionalOnExpression("${app.feature.a:false} && ${app.feature.b:false}")
    public CombinedFeature combinedFeature() {
        return new CombinedFeature();
    }
    
    @Bean
    @ConditionalOnExpression("${app.feature.a:false} || ${app.feature.b:false}")
    public PartialFeature partialFeature() {
        return new PartialFeature();
    }
}

四、@DependsOn:控制 Bean 初始化顺序

4.1 问题场景

你是否遇到过这种情况?

@Component
public class ResourceManager {
    public ResourceManager() {
        // 初始化外部资源(如加载配置文件、连接外部服务)
        initExternalResource();
    }
}

@Component
public class BusinessService {
    public BusinessService(ResourceManager resourceManager) {
        // 依赖 ResourceManager
        // 但如果 ResourceManager 没有被 Spring 管理呢?
    }
}

或者更复杂的场景:

@Component
public class DatabaseInitializer {
    public DatabaseInitializer() {
        // 初始化数据库 schema
        initSchema();
    }
}

@Component
public class Repository {
    // 依赖数据库已经初始化
    // 但 Spring 可能先初始化 Repository
}

4.2 解决方案:@DependsOn

@Component
public class DatabaseInitializer {
    public DatabaseInitializer() {
        initSchema();
    }
}

@Component
@DependsOn("databaseInitializer")
public class Repository {
    public Repository() {
        // 此时 DatabaseInitializer 已经初始化完成
    }
}

4.3 多个依赖

@Component
@DependsOn({"databaseInitializer", "cacheInitializer", "mqInitializer"})
public class ApplicationStartup {
    public ApplicationStartup() {
        // 所有依赖都已初始化
    }
}

4.4 与 @Order 的区别

注解作用适用场景
@Order定义 Bean 的优先级多个 Bean 实现同一接口
@DependsOn强制 Bean 初始化顺序显式依赖关系
@Component
@Order(1)
public class FirstBean {
}

@Component
@Order(2)
public class SecondBean {
    // 不保证 FirstBean 先初始化!
}

@Order 只定义优先级,不保证初始化顺序。而 @DependsOn 是强制的。

4.5 实际应用场景

场景一:初始化外部资源

@Component
public class ExternalResourceLoader {
    private static ExternalResource resource;
    
    public ExternalResourceLoader() {
        resource = loadResourceFromFileSystem();
    }
    
    public static ExternalResource getResource() {
        return resource;
    }
}

@Component
@DependsOn("externalResourceLoader")
public class ResourceConsumer {
    public ResourceConsumer() {
        ExternalResource resource = ExternalResourceLoader.getResource();
        // 使用资源
    }
}

场景二:多数据源初始化

@Component
public class PrimaryDataSourceInitializer {
    public PrimaryDataSourceInitializer() {
        initPrimaryDataSource();
    }
}

@Component
public class SecondaryDataSourceInitializer {
    public SecondaryDataSourceInitializer() {
        initSecondaryDataSource();
    }
}

@Component
@DependsOn({"primaryDataSourceInitializer", "secondaryDataSourceInitializer"})
public class DataSourceRouter {
    public DataSourceRouter() {
        // 两个数据源都已初始化
    }
}

五、综合实战示例

5.1 配置类

@Data
@Validated
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    
    private Environment env = Environment.DEVELOPMENT;
    
    private Database database = new Database();
    
    private Features features = new Features();
    
    @Data
    public static class Database {
        @NotNull
        private String url;
        
        @NotNull
        private String username;
        
        @NotNull
        private String password;
        
        @Min(1)
        @Max(100)
        private int poolSize = 10;
    }
    
    @Data
    public static class Features {
        private boolean monitoring = false;
        private boolean caching = true;
        private boolean async = false;
    }
    
    public enum Environment {
        DEVELOPMENT, TEST, PRODUCTION
    }
}

5.2 条件配置

@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
    
    @Bean
    @ConditionalOnExpression("${app.features.monitoring:true} && ${app.env} == 'production'")
    public MonitoringService monitoringService() {
        return new PrometheusMonitoringService();
    }
    
    @Bean
    @ConditionalOnExpression("${app.features.monitoring:true} && ${app.env} != 'production'")
    public MonitoringService devMonitoringService() {
        return new SimpleMonitoringService();
    }
    
    @Bean
    @ConditionalOnExpression("${app.features.caching:true}")
    public CacheManager cacheManager() {
        return new RedisCacheManager();
    }
    
    @Bean
    @DependsOn("cacheManager")
    @ConditionalOnExpression("${app.features.caching:true}")
    public CacheService cacheService(CacheManager cacheManager) {
        return new CacheServiceImpl(cacheManager);
    }
}

5.3 YAML 配置

app:
  env: production
  database:
    url: jdbc:mysql://localhost:3306/example
    username: admin
    password: password
    pool-size: 20
  features:
    monitoring: true
    caching: true
    async: false

六、效果对比

指标传统方式使用注解后提升
配置代码行数50+20-60%
条件判断复杂度显著降低
初始化顺序控制困难简单100% 可控
类型安全完整类型检查
代码可维护性显著提升

七、总结

核心要点

  1. @ConfigurationProperties:类型安全配置绑定,配合 JSR-303 校验,让配置管理更规范
  2. @ConditionalOnExpression:用 SpEL 表达式写复杂条件,比 @ConditionalOnProperty 灵活得多
  3. @DependsOn:显式控制 Bean 初始化顺序,解决依赖外部资源的尴尬场景

最佳实践

  • 配置类优先使用 @ConfigurationProperties 而非 @Value
  • 复杂条件判断使用 @ConditionalOnExpression
  • 明确依赖关系时使用 @DependsOn
  • 配合 @Validated 做配置校验

互动话题:你在实际项目中用过这三个注解吗?有没有遇到过什么坑?欢迎在评论区分享你的经验!


标题:3个被 90% 程序员忽略的 Spring Boot 注解,用了少写一半代码
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/07/11/1783159659380.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消