SpringBoot | @Value 和 @ConfigurationProperties 的区别

微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。前言最近有跳槽的想法,所以故意复习了下 SpringBoot 的相关知识,复习得比较细。其中有些,我感觉是以前忽略掉的东西,比如 @Value 和 @ConfigurationProperties 的区别 。如何使用定义两个对象,一个学生对象,对应着一个老师对象,代码如下:@ConfigurationProperties学生类@Component@ConfigurationProperties(prefix = “student”) // 指定配置文件中的 student 属性与这个 bean绑定public class Student { private String firstName; private String lastName; private Integer age; private String gender; private String city; private Teacher teacher; private List<String> hobbys; private Map<String,Integer> scores; //注意,为了测试必须重写 toString 和 get,set 方法}老师类public class Teacher { private String name; private Integer age; private String gender; //注意,为了测试必须重写 toString 和 get,set 方法}测试类@RunWith(SpringRunner.class)@SpringBootTestpublic class SpringbootValConproDemoApplicationTests { @Autowired private Student student; @Test public void contextLoads() { // 这里为了方便,但工作中千万不能用 System.out System.out.println(student.toString()); }}输出结果Student{firstName=‘陈’, lastName=‘一个优秀的废人’, age=24, gender=‘男’, city=‘广州’, teacher=Teacher{name=‘eses’, age=24, gender=‘女’}, hobbys=[篮球, 羽毛球, 兵兵球], scores={java=100, Python=99, C=99}}@Value@Value 支持三种取值方式,分别是 字面量、${key}从环境变量、配置文件中获取值以及 #{SpEL}学生类@Component//@ConfigurationProperties(prefix = “student”) // 指定配置文件中的 student 属性与这个 bean绑定public class Student { /** * <bean class=“Student”> * <property name=“lastName” value=“字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> / @Value(“陈”) // 字面量 private String firstName; @Value("${student.lastName}”) // 从环境变量、配置文件中获取值 private String lastName; @Value("#{122}") // #{SpEL} private Integer age; private String gender; private String city; private Teacher teacher; private List<String> hobbys; private Map<String,Integer> scores; //注意,为了测试必须重写 toString 和 get,set 方法}测试结果Student{firstName=‘陈’, lastName=‘一个优秀的废人’, age=24, gender=‘null’, city=‘null’, teacher=null, hobbys=null, scores=null}区别二者区别@ConfigurationProperties@Value功能批量注入配置文件中的属性一个个指定松散绑定(松散语法)支持不支持SpEL不支持支持JSR303数据校验支持不支持复杂类型封装支持不支持从上表可以看见,@ConfigurationProperties 和 @Value 主要有 5 个不同,其中第一个功能上的不同,上面已经演示过。下面我来介绍下剩下的 4 个不同。松散语法松散语法的意思就是一个属性在配置文件中可以有多个属性名,举个栗子:学生类当中的 firstName 属性,在配置文件中可以叫 firstName、first-name、first_name 以及 FIRST_NAME。 而 @ConfigurationProperties 是支持这种命名的,@Value 不支持。下面以 firstName 为例,测试一下。如下代码:@ConfigurationProperties学生类的 firstName 属性在 yml 文件中被定义为 first_name:student: first_name: 陈 # 学生类的 firstName 属性在 yml 文件中被定义为 first_name lastName: 一个优秀的废人 age: 24 gender: 男 city: 广州 teacher: {name: eses,age: 24,gender: 女} hobbys: [篮球,羽毛球,兵兵球] scores: {java: 100,Python: 99,C++: 99}学生类:@Component@ConfigurationProperties(prefix = “student”) // 指定配置文件中的 student 属性与这个 bean绑定public class Student { /** * <bean class=“Student”> * <property name=“lastName” value=“字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> / //@Value(“陈”) // 字面量 private String firstName; //@Value("${student.lastName}”) // 从环境变量、配置文件中获取值 private String lastName; //@Value("#{122}") // #{SpEL} private Integer age; private String gender; private String city; private Teacher teacher; private List<String> hobbys; private Map<String,Integer> scores; //注意,为了测试必须重写 toString 和 get,set 方法}测试结果:Student{firstName=‘陈’, lastName=‘一个优秀的废人’, age=24, gender=‘男’, city=‘广州’, teacher=Teacher{name=‘eses’, age=24, gender=‘女’}, hobbys=[篮球, 羽毛球, 兵兵球], scores={java=100, Python=99, C=99}}@Value学生类:@Component//@ConfigurationProperties(prefix = “student”) // 指定配置文件中的 student 属性与这个 bean绑定public class Student { /** * <bean class=“Student”> * <property name=“lastName” value=“字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> / //@Value(“陈”) // 字面量 @Value("${student.firstName}”) private String firstName; //@Value("${student.lastName}") // 从环境变量、配置文件中获取值 private String lastName; //@Value("#{122}") // #{SpEL} private Integer age; private String gender; private String city; private Teacher teacher; private List<String> hobbys; private Map<String,Integer> scores; //注意,为了测试必须重写 toString 和 get,set 方法}测试结果:启动报错,找不到 bean。从上面两个测试结果可以看出,使用 @ConfigurationProperties 注解时,yml 中的属性名为 last_name 而学生类中的属性为 lastName 但依然能取到值,而使用 @value 时,使用 lastName 确报错了。证明 @ConfigurationProperties 支持松散语法,@value 不支持。SpELSpEL 使用 #{…} 作为定界符 , 所有在大括号中的字符都将被认为是 SpEL , SpEL 为 bean 的属性进行动态赋值提供了便利。@Value如上述介绍 @Value 注解使用方法时,有这样一段代码:@Value("#{122}") // #{SpEL}private Integer age;证明 @Value 是支持 SpEL 表达式的。@ConfigurationProperties由于 yml 中的 # 被当成注释看不到效果。所以我们新建一个 application.properties 文件。把 yml 文件内容注释,我们在 properties 文件中把 age 属性写成如下所示:student.age=#{122}把学生类中的 @ConfigurationProperties 注释打开,注释 @value 注解。运行报错, age 属性匹配异常。说明 @ConfigurationProperties 不支持 SpELJSR303 数据校验@Value加入 @Length 校验:@Component@Validated//@ConfigurationProperties(prefix = “student”) // 指定配置文件中的 student 属性与这个 bean绑定public class Student { /** * <bean class=“Student”> * <property name=“lastName” value=“字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> / //@Value(“陈”) // 字面量 @Value("${student.first-name}”) @Length(min=5, max=20, message=“用户名长度必须在5-20之间”) private String firstName; //@Value("${student.lastName}") // 从环境变量、配置文件中获取值 private String lastName; //@Value("#{122}") // #{SpEL} private Integer age; private String gender; private String city; private Teacher teacher; private List<String> hobbys; private Map<String,Integer> scores;}yaml:student: first_name: 陈测试结果:Student{firstName=‘陈’, lastName=‘null’, age=null, gender=‘null’, city=‘null’, teacher=null, hobbys=null, scores=null}yaml 中的 firstname 长度为 1 。而检验规则规定 5-20 依然能取到属性,说明检验不生效,@Value 不支持 JSR303 数据校验@ConfigurationProperties学生类:@Component@Validated@ConfigurationProperties(prefix = “student”) // 指定配置文件中的 student 属性与这个 bean绑定public class Student { /** * <bean class=“Student”> * <property name=“lastName” value=“字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> / //@Value(“陈”) // 字面量 //@Value("${student.first-name}”) @Length(min=5, max=20, message=“用户名长度必须在5-20之间”) private String firstName; //@Value("${student.lastName}") // 从环境变量、配置文件中获取值 private String lastName; //@Value("#{122}") // #{SpEL} private Integer age; private String gender; private String city; private Teacher teacher; private List<String> hobbys; private Map<String,Integer> scores;}测试结果:报错[firstName],20,5]; default message [用户名长度必须在5-20之间]校验生效,支持 JSR303 数据校验。复杂类型封装复杂类型封装指的是,在对象以及 map (如学生类中的老师类以及 scores map)等属性中,用 @Value 取是取不到值,比如:@Component//@Validated//@ConfigurationProperties(prefix = “student”) // 指定配置文件中的 student 属性与这个 bean绑定public class Student { /** * <bean class=“Student”> * <property name=“lastName” value=“字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> / //@Value(“陈”) // 字面量 //@Value("${student.first-name}”) //@Length(min=5, max=20, message=“用户名长度必须在5-20之间”) private String firstName; //@Value("${student.lastName}") // 从环境变量、配置文件中获取值 private String lastName; //@Value("#{122}") // #{SpEL} private Integer age; private String gender; private String city; @Value("${student.teacher}") private Teacher teacher; private List<String> hobbys; @Value("${student.scores}") private Map<String,Integer> scores;}这样取是报错的。而上文介绍 @ConfigurationProperties 和 @Value 的使用方法时已经证实 @ConfigurationProperties 是支持复杂类型封装的。也就是说 yaml 中直接定义 teacher 以及 scores 。 @ConfigurationProperties 依然能取到值。怎么选用?如果说,只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用 @Value;比如,假设现在学生类加多一个属性叫 school 那这个属性对于该校所有学生来说都是一样的,但防止我这套系统到了别的学校就用不了了。那我们可以直接在 yml 中给定 school 属性,用 @Value 获取。当然上述只是举个粗暴的例子,实际开发时,school 属性应该是保存在数据库中的。如果说,专门编写了一个 javaBean 来和配置文件进行映射,我们就直接使用 @ConfigurationProperties。完整代码https://github.com/turoDog/Demo/tree/master/springboot_val_conpro_demo如果觉得对你有帮助,请给个 Star 再走呗,非常感谢。后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

March 17, 2019 · 4 min · jiezi

SpringBoot注入数据的方式

关于注入数据说明1.不通过配置文件注入数据通过@Value将外部的值动态注入到Bean中,使用的情况有:注入普通字符串注入操作系统属性注入表达式结果注入其他Bean属性:注入Student对象的属性name注入文件资源注入URL资源辅助代码package com.hannpang.model;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Component(value = “st”)//对student进行实例化操作public class Student { @Value(“悟空”) private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}测试@Value的代码package com.hannpang.model;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.Resource;import org.springframework.stereotype.Component;@Componentpublic class SimpleObject { @Value(“注入普通字符串”) private String normal; //关于属性的KEY可以查看System类说明 @Value("#{systemProperties[‘java.version’]}")//–>使用了SpEL表达式 private String systemPropertiesName; // 注入操作系统属性 @Value("#{T(java.lang.Math).random()80}")//获取随机数 private double randomNumber; //注入表达式结果 @Value("#{1+2}") private double sum; //注入表达式结果 1+2的求和 @Value(“classpath:os.yaml”) private Resource resourceFile; // 注入文件资源 @Value(“http://www.baidu.com”) private Resource testUrl; // 注入URL资源 @Value("#{st.name}") private String studentName; //省略getter和setter方法 @Override public String toString() { return “SimpleObject{” + “normal=’” + normal + ‘'’ + “, systemPropertiesName=’” + systemPropertiesName + ‘'’ + “, randomNumber=” + randomNumber + “, sum=” + sum + “, resourceFile=” + resourceFile + “, testUrl=” + testUrl + “, studentName=’” + studentName + ‘'’ + ‘}’; }}Spring的测试代码package com.hannpang;import com.hannpang.model.SimpleObject;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)@SpringBootTestpublic class Demo04BootApplicationTests { @Autowired private SimpleObject so; @Test public void contextLoads() { System.out.println(so); }}运行结果为:SimpleObject{normal=‘注入普通字符串’, systemPropertiesName=‘1.8.0_172’, randomNumber=56.631954541947266, sum=3.0, resourceFile=class path resource [os.yaml], testUrl=URL [http://www.baidu.com], studentName=‘悟空’}2.通过配置文件注入数据通过@Value将外部配置文件的值动态注入到Bean中。配置文件主要有两类:application.properties、application.yaml application.properties在spring boot启动时默认加载此文件自定义属性文件。自定义属性文件通过@PropertySource加载。@PropertySource可以同时加载多个文件,也可以加载单个文件。如果相同第一个属性文件和第二属性文件存在相同key,则最后一个属性文件里的key启作用。加载文件的路径也可以配置变量,如下文的${anotherfile.configinject},此值定义在第一个属性文件config.properties在application.properties中加入如下测试代码app.name=一步教育在resources下面新建第一个属性文件config.properties内容如下book.name=西游记anotherfile.configinject=system在resources下面新建第二个属性文件config_system.properties内容如下我的目的是想system的值使用第一个属性文件中定义的值book.name.author=吴承恩下面通过@Value(“${app.name}”)语法将属性文件的值注入bean属性值,详细代码见:package com.hannpang.test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.PropertySource;import org.springframework.core.env.Environment;import org.springframework.stereotype.Component;@Component@PropertySource(value = {“classpath:config.properties”,“classpath:config_${anotherfile.configinject}.properties”})public class LoadPropsTest { @Value("${app.name}") private String appName; // 这里的值来自application.properties,spring boot启动时默认加载此文件 @Value("${book.name}") private String bookName; // 注入第一个配置外部文件属性 @Value("${book.name.author}") private String author; // 注入第二个配置外部文件属性 @Autowired private Environment env; // 注入环境变量对象,存储注入的属性值 //省略getter和setter方法 public void setAuthor(String author) { this.author = author; } @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append(“bookName=”).append(bookName).append("\r\n") .append(“author=”).append(author).append("\r\n") .append(“appName=”).append(appName).append("\r\n") .append(“env=”).append(env).append("\r\n") // 从eniroment中获取属性值 .append(“env=”).append(env.getProperty(“book.name.author”)).append("\r\n"); return sb.toString(); }}测试代码package com.hannpang;import com.hannpang.model.SimpleObject;import com.hannpang.test.LoadPropsTest;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)@SpringBootTestpublic class Demo04BootApplicationTests { @Autowired private LoadPropsTest lpt; @Test public void loadPropertiesTest() { System.out.println(lpt); }}运行结果为:bookName=西游记author=吴承恩appName=一步教育env=StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[ConfigurationPropertySourcesPropertySource {name=‘configurationProperties’}, MapPropertySource {name=‘Inlined Test Properties’}, MapPropertySource {name=‘systemProperties’}, OriginAwareSystemEnvironmentPropertySource {name=‘systemEnvironment’}, RandomValuePropertySource {name=‘random’}, OriginTrackedMapPropertySource {name=‘applicationConfig: [classpath:/application.properties]’}, ResourcePropertySource {name=‘class path resource [config_system.properties]’}, ResourcePropertySource {name=‘class path resource [config.properties]’}]}env=吴承恩3. #{…}和${…}的区别演示A.${…}的用法{}里面的内容必须符合SpEL表达式,通过@Value(“${app.name}”)可以获取属性文件中对应的值,但是如果属性文件中没有这个属性,则会报错。可以通过赋予默认值解决这个问题,如@Value("${app.name:胖先森}")部分代码// 如果属性文件没有app.name,则会报错// @Value("${app.name}")// private String name;// 使用app.name设置值,如果不存在则使用默认值@Value("${app.name:胖先森}")private String name;B.#{…}的用法部分代码直接演示// SpEL:调用字符串Hello World的concat方法@Value("#{‘Hello World’.concat(’!’)}")private String helloWorld;// SpEL: 调用字符串的getBytes方法,然后调用length属性@Value("#{‘Hello World’.bytes.length}")private String helloWorldbytes;C.#{…}和${…}混合使用${…}和#{…}可以混合使用,如下文代码执行顺序:通过${server.name}从属性文件中获取值并进行替换,然后就变成了 执行SpEL表达式{‘server1,server2,server3’.split(‘,’)}。// SpEL: 传入一个字符串,根据",“切分后插入列表中, #{}和${}配置使用(注意单引号,注意不能反过来${}在外面,#{}在里面)@Value(”#{’${server.name}’.split(’,’)}")private List<String> servers;在上文中在#{}外面,${}在里面可以执行成功,那么反过来是否可以呢${}在外面,#{}在里面,如代码// SpEL: 注意不能反过来${}在外面,#{}在里面,这个会执行失败@Value("${#{‘HelloWorld’.concat(’’)}}")private List<String> servers2;答案是不能。因为spring执行${}是时机要早于#{}。在本例中,Spring会尝试从属性中查找#{‘HelloWorld’.concat(‘’)},那么肯定找到,由上文已知如果找不到,然后报错。所以${}在外面,#{}在里面是非法操作D.用法总结#{…} 用于执行SpEl表达式,并将内容赋值给属性${…} 主要用于加载外部属性文件中的值#{…} 和&dollar;{…} 可以混合使用,但是必须#{}外面,&dollar;{}在里面4.@Value获取值和@ConfigurationProperties获取值比较 @ConfigurationProperties@Value功能批量注入配置文件中的属性一个个指定松散绑定(松散语法)支持不支持SpEL不支持支持JSR303数据校验支持不支持复杂类型封装支持不支持配置文件yml还是properties他们都能获取到值;如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;关于数据校验的部分代码@Component@ConfigurationProperties(prefix = “person”)@Validatedpublic class Person { //lastName必须是邮箱格式 @Email private String lastName;5. @ImportResource引入配置文件不推荐的使用方式Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上@ImportResource(locations = {“classpath:beans.xml”})导入Spring的配置文件让其生效编写配置文件信息<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns=“http://www.springframework.org/schema/beans" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id=“helloService” class=“com.hanpang.springboot.service.HelloService”></bean></beans>大概了解就好,我们基本上不使用这种方式6.@Configuration注解SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式1、配置类@Configuration作用于类上,相当于一个xml配置文件2、使用@Bean给容器中添加组件,作用于方法上/* * @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件 * * 在配置文件中用<bean><bean/>标签添加组件 * <bean id=“helloService” class=“com.hanpang.springboot.service.HelloService”></bean> /@Configurationpublic class MyAppConfig { //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名 @Bean public HelloService helloService02(){ System.out.println(“配置类@Bean给容器中添加组件了…”); return new HelloService(); }}使用Bean注入太麻烦,我们更加喜欢使用扫描的方式import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration; import com.wx.dao.IUserDao;import com.wx.dao.UserDaoImpl; //通过该注解来表明该类是一个Spring的配置,相当于一个传统的ApplicationContext.xml@Configuration//相当于配置文件里面的<context:component-scan/>标签,扫描这些包下面的类的注解@ComponentScan(basePackages=“com.hanpang.dao,com.hanpang.service”)public class SpringConfig { // 通过该注解来表明是一个Bean对象,相当于xml中的<bean> //bean的id值默认是方法名userDao / @Bean public HelloService helloService02(){ System.out.println(“配置类@Bean给容器中添加组件了…”); return new HelloService(); } */}附录随机数${random.value}、${random.int}、${random.long}${random.int(10)}、${random.int[1024,65536]} ...

December 23, 2018 · 2 min · jiezi

element-ui中cascader同时获取label和value值

关于elementUI中cascader选中值后,能获取value或者label,但不能同时获value和label,这一问题,琢磨出了这么个办法。以新增和编辑城市为例,type: 1 编辑,type: 0 新增1. 配置元素<el-cascader filterable :class="{‘city-cascader’: type==1}" :placeholder=“city || ‘请选择’” :options=“cityLists” :props=“cityProps” v-model=“citySelected” style=“width:300px;” :show-all-levels=“false” @change=“changeCity” ></el-cascader>2. 配置cityPropscityProps: {value: ‘all’, label: ’label’}3. 组装props中的all// cityLists中遍历组装allall: { value: value, label: label}4. 使用此时,点击cascader选择需要的内容后,取出来的citySelected值就是[{value: 选中值的value, label: 选中值的label}]这个方法可以通过配置all获取任意自己想要的值。PS: 关于拿不到默认值的问题,我投机取巧的使用了placeholder。:placeholder=“city || ‘请选择’“然后在cascader上加上样式::class=”{‘city-cascader’: type==1}".city-cascader .el-input__inner::placeholder { color: #333 !important;}ok,完美解决cascader取值问题。

November 16, 2018 · 1 min · jiezi