SpringBoot系列之@ConfigurationProperties VS @Value注解(三)
前言
上一节我们通过注解@PropertySource读取内外部配置文件,然后通过注解@Value读取其值,在Spring中通过注解@ConfigurationProperties也可以读取配置文件中的值,接下来我们一起来看看注解@ConfigurationProperties和@Value有何区别。
@ConfigurationProperties VS @Value注解
关于注解@Value上一节我们已经详细讨论过,那么我们首先来分析注解@ConfigurationProperties,接下来我们在配置文件application.properties中给出一段我们需要读取的值,如下:
#app app.trade-currency=USD app.refresh-time-unit=seconds app.refresh-rate=3
接下来我们在创建的配置文件类Spring.Config中来读取值,如下:
package com.demo.springboot; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Currency; import java.util.concurrent.TimeUnit; @Configuration @ConfigurationProperties(prefix = "app") public class SpringConfig { private int refreshRate; private TimeUnit refreshTimeUnit; private Currency tradeCurrency; @Bean public UserDAL getUserDAL() { return new UserDAL(); } @Override public String toString() { System.out.println(); return "SpringConfig:{" + "refreshRate = '" + refreshRate + '\'' + ", refreshTimeUnit = '" + refreshTimeUnit + '\'' + ", tradeCurrency = '" + tradeCurrency + '\'' + '}'; } }
如上我们并未添加注解@PropertySource指定读取文件位置,因为默认会从默认创建的配置文件application.properties中读取。注解@ConfigurationProperties需要明确参数prefix(前缀),而且不能为空,我们看到在配置文件中所给出的值的前缀都为app,所以如上我们给出其参数前缀为app,接下来我们调用该配置文件类并打印出映射结果,如下:
package com.demo.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootApplication.class, args); SpringConfig bean = context.getBean(SpringConfig.class); System.out.println(bean); } }
如上我们可以看到值都为默认值并未映射上,这是为什么呢?因为通过注解@ConfigurationProperties绑定字段时,必须要使用属性。如下我们添加属性:
public int getRefreshRate() { return refreshRate; } public void setRefreshRate(int refreshRate) { this.refreshRate = refreshRate; } public TimeUnit getRefreshTimeUnit() { return refreshTimeUnit; } public void setRefreshTimeUnit(TimeUnit refreshTimeUnit) { this.refreshTimeUnit = refreshTimeUnit; } public Currency getTradeCurrency() { return tradeCurrency; } public void setTradeCurrency(Currency tradeCurrency) { this.tradeCurrency = tradeCurrency; }
既然注解@ConfigurationProperties通过属性来绑定,那么对于我们在配置文件中的名称是否有大小写要求或者说必须精确匹配呢? 注解@ConfigurationProperties对属性绑定遵循relaxed bind rule【暂且翻译为松散绑定规则】,并不需要精确匹配。比如对属性【app.username】,通过【app.userName】、【app.user-name】、【app.user_name】、【app.USER_NAME】、【app.USER-NAME】等都可匹配,我们可理解为模糊匹配。上述我们看到时间值我们声明的是小写,但最终翻译成了大写,但是对于上述货币而言,我们必须定义成大写,在配置文件中不能定义成小写或者大小写混用,比如在配置文件中我们声明其值为usd,否则将抛出如下异常。
对于注解@Value而言,占位符必须严格和配置文件中对应键一致,否则抛出无法解析异常。到此我们可以知道若一个类中有很多字段,那么必须在每一个字段上都添加@Value注解,如此一来比较繁琐,对少数字段还是比较友好,其实呢应该推荐使用注解@ConfigurationProperties,因为少数字段映射我们完全可借助注解@Environment接口获取。一言以蔽之,我们总结下注解@ConfigurationProperties和@Value的区别: @ConfigurationProperties用于使用POJO bean映射属性,而注解@Value通过键注入特定的属性值。
深入探讨注解@ConfigurationProperties
上述我们只是讨论了注解@ConfigurationProperties的使用方式,接下来我们来探讨下该注解正确使用姿势,上述我们在使用该注解时,我们发现同时添加了注解@Configuration,要是我们不添加会报错如下:
此时又多出一个注解@EnableConfigurationProperties,难道是在使用注解@ConfigurationProperties时,必须添加该注解,说明我们要启用该注解或者添加注解@Component吗?显然不是这样,使用注解@Configuration说明我们要使用bean, 我们知道注解@ConfigurationProperties是进行POJO映射,所以是干净的一个原始实体,完全不用再添加其他注解,这里报错我们暂且不管,我们改造如下:
package com.demo.springboot; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Currency; import java.util.concurrent.TimeUnit; @ConfigurationProperties(prefix = "app") public class SpringConfig { private int refreshRate; private TimeUnit refreshTimeUnit; private Currency tradeCurrency; public int getRefreshRate() { return refreshRate; } public void setRefreshRate(int refreshRate) { this.refreshRate = refreshRate; } public TimeUnit getRefreshTimeUnit() { return refreshTimeUnit; } public void setRefreshTimeUnit(TimeUnit refreshTimeUnit) { this.refreshTimeUnit = refreshTimeUnit; } public Currency getTradeCurrency() { return tradeCurrency; } public void setTradeCurrency(Currency tradeCurrency) { this.tradeCurrency = tradeCurrency; } @Override public String toString() { System.out.println(); return "SpringConfig:{" + "refreshRate = '" + refreshRate + '\'' + ", refreshTimeUnit = '" + refreshTimeUnit + '\'' + ", tradeCurrency = '" + tradeCurrency + '\'' + '}'; } }
假设上述是读取的是连接数据库的相关配置,接下来我们创建如下类:
package com.demo.springboot; public class DbConnectionConfiguration { ...... }
我们创建上述类要连接数据库并进行相关操作,此时则需要用到上述数据库配置类,此时就要用到注解@EnableConfigurationProperties,通过添加该注解表示要查找并注册注解为@ConfigurationProperties作为bean,同时呢我们也要添加注解@Configuration提供进行数据源的bean,如下:
package com.demo.springboot; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(SpringConfig.class) public class DbConnectionConfiguration { private SpringConfig springConfig; public DbConnectionConfiguration(SpringConfig springConfig) { this.springConfig = springConfig; } @Bean public DataSource dataSource() { ...... } }
此时我们将发现Spring.Config不再报错,因为上述我们使用Spring.Config作为了一个bean,否则在开始时,我们看到报错,立马添加注解@Component,接下来运行将抛出Spring.Config已多次被注解为bean,如下:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: ...... available: expected single matching bean but found 2: springConfig,app-com.demo.springboot.SpringConfig
相信通过上述我的举例,阅读本文的您能够明白这几者的区别所在了,在这里呢,我们可以下一个结论: 注解@ConfigurationProperties用于将类与外部属性配置文件绑定,因为其完全属于POJO,所以必须将Bean类与配置实体类隔离开。而在这种情况下注解@Configuration用于创建配置POJO的Spring bean。@EnableConfigurationProperties用于在配置实体类和Spring配置之间创建绑定,以便在注入服务内部之后可以轻松地检索到对应属性。
总结
本节我们重点讨论了注解@ConfigurationProperties的使用,对于批量属性映射POJO,很明显会通过注解@ConfigurationProperties操作,相对注解@Value而言,它更加灵活,我想这也是推荐使用该注解的原因,好了,本节我们到此为止,感谢您的阅读,我们下节见。