进阶!使用 @PropertySource 的 7 个高级招式

进阶!使用 @PropertySource 的 7 个高级招式

适用版本:Spring Framework 5.x / 6.x,Spring Boot 2.x/3.x
读完本文,你将彻底告别 “PropertySource 只能加载 properties” 的刻板印象,能在 任意来源、任意格式、动态刷新、多环境 场景下游刃有余。


一、快速回顾:@PropertySource 基础

@Configuration
@PropertySource("classpath:app.properties")
class BasicConfig { }
  • 默认只能加载 properties
  • 默认 不支持 YAML
  • 默认 不自动刷新
  • 默认 按声明顺序覆盖

下面给出 7 个高级用法,逐一打破这些限制。


二、7 个高级招式速查表

# 招式 关键代码 解决痛点
加载 YAML @PropertySource(value="...", factory=YamlPropertySourceFactory.class) 官方不支持的格式
加载 JSON 自定义 PropertySourceFactory + Jackson 微服务配置中心
动态刷新 @PropertySource(..., ignoreResourceNotFound=true) + @RefreshScope Nacos/Apollo 热更新
多环境占位符 @PropertySource("classpath:config-${spring.profiles.active}.yml") 多环境文件
自定义顺序 @Order(Ordered.HIGHEST_PRECEDENCE) + @PropertySource 覆盖优先级
网络/数据库 PropertySourceFactory 里远程拉取 配置中心
条件化加载 @ConditionalOnProperty + 组合注解 按需启用

三、招式详解

① 加载 YAML(最常用)

  1. 自定义工厂
public class YamlPropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
        factory.setResources(resource.getResource());
        Properties props = factory.getObject();
        return new PropertiesPropertySource(resource.getResource().getFilename(), props);
    }
}
  1. 使用
@Configuration
@PropertySource(
    value = "classpath:application.yml",
    factory = YamlPropertySourceFactory.class
)
class YamlConfig { }

② 加载 JSON(示例)

public class JsonPropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        JsonNode root = new ObjectMapper().readTree(resource.getInputStream());
        Properties props = new Properties();
        flatten("", root, props);           // 递归展开
        return new PropertiesPropertySource(name != null ? name : "json", props);
    }

    private void flatten(String prefix, JsonNode node, Properties props) {
        if (node.isObject()) {
            node.fields().forEachRemaining(e ->
                flatten(prefix.isEmpty() ? e.getKey() : prefix + "." + e.getKey(), e.getValue(), props));
        } else {
            props.put(prefix, node.asText());
        }
    }
}

③ 动态刷新(Nacos/Apollo)

@Configuration
@PropertySource(
    value = "classpath:dynamic.yml",
    factory = YamlPropertySourceFactory.class,
    ignoreResourceNotFound = true
)
@RefreshScope
class DynamicConfig {
    @Value("${feature.x}")
    private boolean featureX;
}
  • 文件变化或配置中心推送后,@RefreshScope Bean 会重新注入新值。

④ 占位符 + 多环境

@Configuration
@PropertySource(
    value = "classpath:config-${spring.profiles.active}.yml",
    factory = YamlPropertySourceFactory.class,
    ignoreResourceNotFound = true   // 文件不存在也不抛异常
)
class EnvConfig { }

启动参数示例:

java -jar app.jar --spring.profiles.active=prod

⑤ 显式顺序(覆盖优先级)

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
@PropertySource("classpath:override.properties")
class OverrideConfig { }

⑥ 网络/数据库配置源

public class RemotePropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        String url = resource.getResource().getURI().toString();
        Properties props = new RestTemplate()
            .exchange(url, HttpMethod.GET, null, Properties.class).getBody();
        return new PropertiesPropertySource("remote", props);
    }
}

使用:

@PropertySource(
  value = "http://config-center/app.properties",
  factory = RemotePropertySourceFactory.class
)

⑦ 条件化加载

@Retention(RetentionPolicy.RUNTIME)
@PropertySource(value = "classpath:audit.yml", factory = YamlPropertySourceFactory.class)
@ConditionalOnProperty(name = "audit.enabled", havingValue = "true")
@interface EnableAuditConfig { }

@Configuration
@EnableAuditConfig
class AuditConfig { }

四、常见坑 & 排查

现象 原因 解决方案
YAML 不生效 未指定 factory 引入 YamlPropertySourceFactory
占位符未解析 属性源加载顺序不对 使用 @AutoConfigureBefore 调整
刷新不生效 缺少 @RefreshScope 给需要刷新的 Bean 加注解
读取为 null ignoreResourceNotFound=true 导致文件不存在 检查路径或占位符

五、一句话总结

@PropertySource 不只是加载 .properties,通过 自定义工厂 + 占位符 + 条件化 + 动态刷新,你可以把 任意来源、任意格式 的配置注入 Spring,真正做到 “配置即代码,代码可配置”

你可能感兴趣的:(SpringBoot,高效的Java开发实践,spring,boot,java,后端)