Springboot读取yaml配置文件的三种方式(以及static注入失效的解决方式和原理)

本文详细解析了在SpringBoot中使用@Value、@ConfigurationProperties和Environment读取配置文件的方法,对比了不同注解的正确与错误用法,并介绍了如何避免static修饰符导致的注入失败。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

初衷:
1.最近在搭建springboot项目的时候,使用static修饰属性遇到读取配置文件失败。
2.总结一下对项目配置文件整体或者拆分处理
注意:@PropertySource方式只对properties文件可以进行加载,但对于yml或者yaml不能支持。
首先看一下yaml部分配置:

spring:
  # 数据库配置
  datasource:
    druid:
      driver-class-name: oracle.jdbc.OracleDriver
      url: jdbc:oracle:thin:@xxx.x.x.x:1521/orcl
      username: scott
      password: 12345

正文

1.@value+@Configuration对配置的全局处理
1.1 正确写法

1.新建全局配置类Confignature,用于配置整个yaml的配置信息。(正确写法)
由下面可以看到,有2种写法
(1)直接在属性上面@value注解(代码简洁,但是每次使用需要先new一个Confignature对象,或者@Autowired先注入):driver-class-name
(2)属性上面加static关键字修饰,@value写在对应set方法上面(不需要new,可以其他类直接调用):url,username,password

@Configuration
public class Confignature {

    @Value(value = "${spring.datasource.druid.driver-class-name}")
    public String DATASOURCE_DRUID_DRIVER_CLASS_NAME; //oracle驱动

    public static String DATASOURCE_DRUID_URL; //oracle url

    public static String DATASOURCE_DRUID_USERNAME; // oracle 用户名

    public static String DATASOURCE_DRUID_PASSWORD; // oracle 密码

    @Value("${spring.datasource.druid.url}")
    public void setDatasourceDruidUrl(String datasourceDruidUrl) {
        DATASOURCE_DRUID_URL = datasourceDruidUrl;
    }

    @Value("${spring.datasource.druid.username}")
    public void setDatasourceDruidUsername(String datasourceDruidUsername) {
        DATASOURCE_DRUID_USERNAME = datasourceDruidUsername;
    }

    @Value("${spring.datasource.druid.password}")
    public void setDatasourceDruidPassword(String datasourceDruidPassword) {
        DATASOURCE_DRUID_PASSWORD = datasourceDruidPassword;
    }
}

2.写一个controller测试一下

    @Autowired
    private Confignature confignature;

    @GetMapping("/testSpringBoot")
    @ResponseBody
    public void testSpringBoot() {
    	// driver属性需要先注入confignature配置类
        System.out.println(confignature.DATASOURCE_DRUID_DRIVER_CLASS_NAME);
        // url,usernam,password属性可以直接调用
        System.out.println(Confignature.DATASOURCE_DRUID_URL);
        System.out.println(Confignature.DATASOURCE_DRUID_USERNAME);
        System.out.println(Confignature.DATASOURCE_DRUID_PASSWORD);

    }

如图所示:成功获取值
在这里插入图片描述

1.2 错误写法

错误写法:
(1)@value注解和static同时在属性上面
(2)static修饰属性,但是set方法用了static修饰

@Configuration
public class Confignature {

    // 第一种错误写法
    @Value(value = "${spring.datasource.druid.driver-class-name}")
    public static String DATASOURCE_DRUID_DRIVER_CLASS_NAME; //oracle驱动

    // 第二种错误写法
    public static String DATASOURCE_DRUID_URL; //oracle url

    // 正确写法
    public static String DATASOURCE_DRUID_USERNAME; // oracle 用户名

    // 正确写法
    public static String DATASOURCE_DRUID_PASSWORD; // oracle 密码

    @Value("${spring.datasource.druid.url}")
    public static void setDatasourceDruidUrl(String datasourceDruidUrl) {
        DATASOURCE_DRUID_URL = datasourceDruidUrl;
    }

    @Value("${spring.datasource.druid.username}")
    public void setDatasourceDruidUsername(String datasourceDruidUsername) {
        DATASOURCE_DRUID_USERNAME = datasourceDruidUsername;
    }

    @Value("${spring.datasource.druid.password}")
    public void setDatasourceDruidPassword(String datasourceDruidPassword) {
        DATASOURCE_DRUID_PASSWORD = datasourceDruidPassword;
    }

}

测试一下:
在这里插入图片描述

2.ConfigurationProperties注解对配置进行拆分处理

1.新建一个配置类DatasourceConfig,注意属性名称要跟yaml配置文件一致。这里罗列正确和错误写法(不能用static修饰属性)
正确:driverClassName,url,username
错误:password
这种写法优势:当yaml配置文件配置信息过多,可以多写几个DatasourceConfig针对不同的配置,这样结构更为清晰

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DatasourceConfig {

    private String driverClassName;

    private String url ;

    private String username;

    // 错误写法
    private static String password;

}

2.新写一个controller,这种方式就必须要@Autowired注入DatasourceConfig 配置类

    @Autowired
    private DatasourceConfig datasourceConfig;

    @GetMapping("/testSpringBoot1")
    @ResponseBody
    public void testSpringBoot1() {
        System.out.println(datasourceConfig.getDriverClassName());
        System.out.println(datasourceConfig.getUrl());
        System.out.println(datasourceConfig.getUsername());

    }

测试一下:
由图可知,取得了3个值,password未能获取,原因在于属性用了static修饰
在这里插入图片描述

3.Environment读取yaml配置文件

1.新写一个controller,注入Environment 类

    @Autowired
    private Environment env;

    @GetMapping("/testSpringBoot2")
    @ResponseBody
    public void testSpringBoot2() {
        System.out.println(env.getProperty("spring.datasource.druid.driver-class-name"));
        System.out.println(env.getProperty("spring.datasource.druid.url"));
        System.out.println(env.getProperty("spring.datasource.druid.username"));
        System.out.println(env.getProperty("spring.datasource.druid.password"));

    }

测试一下:
在这里插入图片描述

pom相关依赖
    <!--配置类相关-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>

    <!--druid相关-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.20</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>

    <!--oracle相关-->
    <dependency>
      <groupId>com.oracle</groupId>
      <artifactId>ojdbc6</artifactId>
      <version>11.2.0.3</version>
    </dependency>

    <!--lomback-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.0</version>
      <scope>provided</scope>
    </dependency>

源码解读为什么@value不能注入static

@Value注解不能注入static修饰的属性

<think>我们讨论的是在Java枚举类中读取YAML配置文件的值。用户之前的问题是关于如何实现,而我的回答给出了几种方法(通过配置类中转、构造函数注入等)。现在用户的新问题是:是否可以在枚举类中使用类似`#{'${xxxx}'}`的语法直接读取YAML内容。首先,需要明确的是,`#{'${xxxx}'}`这种语法看起来像是Spring的SpEL(SpringExpressionLanguage)表达式,通常用于在Spring配置文件中引用属性值(如`@Value("${property}")`)或者更复杂的表达式。但是,枚举类在Java中是静态的,它们的实例在类加载时就被初始化,而Spring的依赖注入通常发生在实例创建之后。因此,我们不能直接在枚举常量的定义中使用Spring的`@Value`注解或者SpEL表达式,因为枚举初始化时Spring容器可能还没有准备好。所以,直接使用`#{'${xxxx}'}`在枚举常量中是不可行的。但是,我们可以通过其他方式间接实现类似的功能。以下是一个替代方案:1.使用`@ConfigurationProperties`将YAML中的值绑定到一个JavaBean中。2.在枚举类中,我们通过一个静态方法或者静态字段来持有这个配置的值,但是要注意初始化顺序。我们可以利用Spring的`ApplicationContextAware`接口或者`@PostConstruct`来在容器启动后设置枚举需要的配置值。然而,由于枚举实例是在类加载时创建的,我们不能在枚举的构造函数中注入Spring管理的Bean。因此,我们需要将配置值的加载延迟到Spring容器启动之后,并且枚举类中的方法在调用时能够获取到这些值。具体步骤:步骤1:定义配置类,读取YAML中的值```java@Component@ConfigurationProperties(prefix="myapp")publicclassMyAppProperties{privateMap<String,String>enumConfig=newHashMap<>();//gettersetterpublicMap<String,String>getEnumConfig(){returnenumConfig;}publicvoidsetEnumConfig(Map<String,String>enumConfig){this.enumConfig=enumConfig;}}```步骤2:创建一个工具类来获取Spring容器中的Bean```java@ComponentpublicclassSpringContextimplementsApplicationContextAware{privatestaticApplicationContextcontext;@OverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{context=applicationContext;}publicstatic<T>TgetBean(Class<T>beanClass){returncontext.getBean(beanClass);}}```步骤3:在枚举类中,通过工具类获取配置Bean,然后根据枚举的名称获取对应的值```javapublicenumMyEnum{VALUE1,VALUE2;publicStringgetConfigValue(){//从Spring容器中获取配置类MyAppPropertiesproperties=SpringContext.getBean(MyAppProperties.class);//根据枚举的名称获取配置的值returnproperties.getEnumConfig().get(this.name());}}```步骤4:在YAML配置文件中,配置如下:```yamlmyapp:enumConfig:VALUE1:"配置值1"VALUE2:"配置值2"```这样,当我们在Spring容器启动后调用`MyEnum.VALUE1.getConfigValue()`时,就能得到"配置值1"。但是,这种方法有一个缺点:每次调用`getConfigValue()`都要从Spring容器中获取Bean,可能会有性能开销。为了避免这个问题,我们可以在枚举类中缓存这个配置Map,但是要注意缓存的更新(如果配置是动态的)。如果我们不需要动态更新,可以在枚举类中这样缓存:```javapublicenumMyEnum{VALUE1,VALUE2;privatestaticMap<String,String>configMap;publicStringgetConfigValue(){if(configMap==null){//初始化配置MyAppPropertiesproperties=SpringContext.getBean(MyAppProperties.class);configMap=properties.getEnumConfig();}returnconfigMap.get(this.name());}}```但是,这种方式在多线程环境下需要注意线程安全问题(不过枚举是单例,且配置一般只在启动时加载一次,所以问题不大)。另外,如果配置是动态的(比如在运行时可能会改变),那么就不能缓存,或者需要监听配置变化来更新缓存。总结:在枚举类中不能直接使用`#{'${}'}`这样的表达式,但可以通过间接的方式(利用Spring容器工具类)在枚举的方法中获取配置值。注意:以上方法要求Spring容器已经初始化,因此不能在Spring容器启动过程中使用枚举的配置值(比如在Bean的初始化方法中,如果这个Bean的初始化早于配置类的加载,可能会出现问题)。通常,我们会在业务代码中调用枚举的方法,此时Spring容器已经启动完成。如果用户坚持想用类似表达式的语法,可以考虑使用自定义注解AOP,但这样会复杂很多,不推荐。因此,回答用户的问题:直接使用`#{'${xxxx}'}`语法在枚举类中不可行,但可以通过上述方法间接实现。相关问题:1.如何在枚举类中实现配置值的热更新?2.有没有办法在枚举初始化时就加载配置值(而不是在方法调用时)?3.如果配置值缺失,如何提供默认值?4.如何避免每次调用方法都去获取Spring容器?5.在非Spring环境中,枚举类如何读取YAML配置?</think>### Java枚举类使用 `#{'${}'}` 语法读取YAML文件分析 **直接结论:Java枚举类中无法直接使用 `#{'${xxxx}'}` 语法读取YAML文件内容**。原因如下: 1. **语法不匹配**: - `#{...}` `${...}` 是**Spring Expression Language (SpEL)** 的语法 - 这些表达式只能在Spring管理的Bean中使用(如 `@Value` 注解) - 枚举类在JVM加载时初始化,早于Spring容器初始化[^2] 2. **枚举初始化限制**: ```java public enum Status { // 编译错误:表达式非法 ACTIVE(#{'${app.status.active}'}), INACTIVE(#{'${app.status.inactive}'}); private final String value; Status(String val) { this.value = val; } } ``` 枚举构造函数**不支持运行时表达式解析**,所有值必须在编译时确定[^4] --- ### 替代解决方案(Spring Boot环境) #### 方法1:配置类中转 + 懒加载 ```java // application.yaml app: status: active: "启用" inactive: "禁用" // 配置类 @Configuration @ConfigurationProperties(prefix = "app") public class AppConfig { private Map<String, String> status = new HashMap<>(); // getter/setter } // 枚举类 public enum Status { ACTIVE, INACTIVE; public String getValue() { // 懒加载获取配置 return SpringContext.getBean(AppConfig.class) .getStatus().get(this.name().toLowerCase()); } } // Spring上下文工具 @Component public class SpringContext implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext ctx) { context = ctx; } public static <T> T getBean(Class<T> beanClass) { return context.getBean(beanClass); } } ``` #### 方法2:静态初始化(非Spring方案) ```java public enum Status { ACTIVE(loadConfig("active")), INACTIVE(loadConfig("inactive")); private final String value; private static String loadConfig(String key) { // 使用JYaml读取配置[^1][^3] YamlDecoder decoder = new YamlDecoder( Status.class.getResourceAsStream("/config.yml") ); try { Map<String, Object> config = (Map) decoder.readObject(); return (String) config.get(key); } catch (IOException e) { throw new RuntimeException("加载配置失败", e); } } } ``` --- ### 关键差异对比表 | 方法 | 支持动态更新 | 依赖Spring | 初始化时机 | 复杂度 | |------------------------|-------------|-----------|---------------|--------| | `#{'${}'}` (不可行) | ❌ | 必需 | 编译时 | - | | 配置类中转 | ✅ | 必需 | 首次调用时 | ★★★ | | JYaml静态初始化 | ❌ | 不需要 | 类加载时 | ★★ | | `@PostConstruct`注入 | ❌ | 必需 | Bean初始化后 | ★★★★ | --- ### 最佳实践建议 1. **简单场景**:使用静态初始化+JYaml(非Spring项目) 2. **Spring项目**: - 优先选配置类中转方案 - 需要热更新时,结合`@RefreshScope`(配置中心) 3. **重要提示**: ```java // 错误示例:直接注入会导致NPE @Autowired // 无法注入枚举实例 private AppConfig config; ``` 枚举实例**不是Spring Bean**,无法直接使用依赖注入[^4] 4. **YAML配置示例**: ```yaml app: status_map: ACTIVE: "运行中" INACTIVE: "已停止" PENDING: "待激活" ``` --- ### 相关问题 1. 如何在枚举初始化前预加载YAML配置? 2. 非Spring项目如何优雅地实现枚举-YAML绑定? 3. 配置中心动态更新时如何刷新枚举值? 4. 如何验证YAML配置与枚举项的完整性? 5. 枚举配置值缺失时应该如何处理? [^1]: JYaml通过流式API实现YAML解析 [^2]: 枚举初始化早于Spring容器导致依赖注入失效 [^3]: YamlEncoder/YamlDecoder提供对象序列化能力 [^4]: 枚举构造函数不支持运行时表达式解析
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值