对于从事java开发工作的小伙伴来说,spring框架肯定再熟悉不过了。spring给开发者提供了非常丰富的api,满足我们日常的工作需求。
如果想要创建bean实例,可以使用@Controller、@Service、@Repository、@Component等注解。
如果想要依赖注入某个对象,可以使用@Autowired和@Resource注解。
如果想要开启事务,可以使用@Transactional注解。
如果想要动态读取配置文件中的某个系统属性,可以使用@Value注解。
等等,还有很多。。。
前面几种常用的注解,在我以往的文章《[@Autowired的这些骚操作,你都知道吗?] 》《[聊聊spring事务失效的12种场景,太坑了] 》《[惊呆了,spring中竟然有12种定义bean的方法] 》中已经介绍过了,在这里就不过多讲解了。
今天咱们重点聊聊@Value
注解,因为它是一个非常有用,但极其容易被忽视的注解,绝大多数人可能只用过它的一部分功能,这是一件非常遗憾的事情。
所以今天有必要和大家一起,重新认识一下@Value
。
假如在UserService类中,需要注入系统属性到userName变量中。通常情况下,我们会写出如下的代码:
@Service
public class UserService {
@Value("${susan.test.userName}")
private String userName;
public String test() {
System.out.println(userName);
return userName;
}
}
通过@Value
注解指定系统属性的名称susan.test.userName
,该名称需要使用${}
包起来。
这样spring就会自动的帮我们把对应的系统属性值,注入到userName变量中。
不过,上面功能的重点是要在applicationContext.properties
文件(简称:配置文件)中配置同名的系统属性:
#张三
susan.test.userName=\u5f20\u4e09
那么,名称真的必须完全相同吗?
这时候,有些朋友可能会说:在@ConfigurationProperties
配置类中,定义的参数名可以跟配置文件中的系统属性名不同。
比如,在配置类MyConfig类中定义的参数名是userName:
@Configuration
@ConfigurationProperties(prefix = "susan.test")
@Data
public class MyConfig {
private String userName;
}
而配置文件中配置的系统属性名是:
susan.test.user-name=\u5f20\u4e09
类中用的userName
,而配置文件中用的user-name
,不一样。但测试之后,发现该功能能够正常运行。
配置文件中的系统属性名用 驼峰标识
或 小写字母加中划线的组合
,spring都能找到配置类中的属性名userName进行赋值。
由此可见,配置文件中的系统属性名,可以跟配置类中的属性名不一样。不过,有个前提,前缀susan.test必须相同。
那么,@Value
注解中定义的系统属性名也可以不一样吗?
答案:不能。如果不一样,启动项目时会直接报错。
此外,如果只在@Value注解中指定了系统属性名,但实际在配置文件中没有配置它,也会报跟上面一样的错。
所以,@Value注解中指定的系统属性名,必须跟配置文件中的相同。
不知道细心的小伙伴们有没有发现,我配置的属性值:张三
,其实是转义
过的。
susan.test.userName=\u5f20\u4e09
为什么要做这个转义?
假如在配置文件中配置中文的张三:
susan.test.userName=张三
最后获取数据时,你会发现userName竟然出现了乱码:
å¼ ä¸
what?
为什么会出现乱码?
答:在springboot的CharacterReader
类中,默认的编码格式是ISO-8859-1
,该类负责.properties
文件中系统属性的读取。如果系统属性包含中文字符,就会出现乱码。
那么,如何解决乱码问题呢?
目前主要有如下三种方案:
显然@Value不支持encoding参数,所以方案2不行。
假如使用方案1,具体实现代码如下:
@Service
public class UserService {
@Value(value = "${susan.test.userName}")
private String userName;
public String test() {
String userName1 = new String(userName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
System.out.println();
return userName1;
}
}
确实可以解决乱码问题。
但如果项目中包含大量中文系统属性值,每次都需要加这样一段特殊转换代码。出现大量重复代码,有没有觉得有点恶心?
反转我被恶心到了。
那么,如何解决代码重复问题呢?
答:将属性值的中文内容转换成unicode。
类似于这样的:
susan.test.userName=\u5f20\u4e09
这种方式同样能解决乱码问题,不会出现恶心的重复代码。但需要做一点额外的转换工作,不过这个转换非常容易,因为有现成的在线转换工具。
推荐使用这个工具转换:http://www.jsons.cn/unicode/
在这里顺便告诉你一个小秘密:如果你使用的是.yml
或.yaml
格式的配置文件,并不会出现中文乱码问题。
这又是为什么?
因为.yml或.yaml格式的配置文件,最终会使用UnicodeReader
类进行解析,它的init
方法中,首先读取BOM文件头信息,如果头信息中有UTF8、UTF16BE、UTF16LE,就采用对应的编码,如果没有,则采用默认UTF8
编码。
需要注意的是:乱码问题一般出现在本地环境,因为本地直接读取的.properties配置文件。在dev、test、生产等环境,如果从zookeeper、apollo、nacos等配置中心中获取系统参数值,走的是另外的逻辑,并不会出现乱码问题。
有时候,默认值是我们非常头疼的问题。
为什么这样说呢?
因为很多时候使用java的默认值,并不能满足我们的日常工作需求。
比如有这样一个需求:如果配置了系统属性,userName就用配置的属性值。如果没有配置,则userName用默认值susan。
有些朋友可能认为可以这样做:
@Value(value = "${susan.test.userName}")
private String userName = "susan";
在定义参数时直接给个默认值,但如果仔细想想这招是行不通的的。因为设置userName默认值的时机,比@Value注解依赖注入属性值要早,也就是说userName初始化好了默认值,后面还是会被覆盖。
那么,到底该如何设置默认值呢?
答:使用:
。
例如:
@Value(value = "${susan.test.userName:susan}")
private String userName;
在需要设置默认值的系统属性名后,加:
符号。紧接着,在:
右边设置默认值。
建议大家平时在使用@Value时,尽量都设置一个默认值。如果不需要默认值,宁可设置一个空。比如:
@Value(value = "${susan.test.userName:}")
private String userName;
为什么这么说?
假如有这种场景:在business层中包含了UserService类,business层被api服务和job服务都引用了。但UserService类中@Value的userName只在api服务中有用,在job服务中根本用不到该属性。
对于job服务来说,如果不在.properties文件中配置同名的系统属性,则服务启动时就会报错。
这个坑,我之前踩过多次。所以,建议大家,使用@Value注解时,最好给参数设置一个默认值,以防止出现类似的问题。
前面我们已经见识过,如何使用@Value注解,给类的成员变量
注入系统属性值
。
那么,问题来了,静态变量
可以自动注入系统属性值不?
我们一起看看,假如将上面的userName定义成static
的:
@Value("${susan.test.userName}")
private static String userName;
程序可以正常启动,但是获取到userName的值却是null。
由此可见,被static
修饰的变量通过@Value会注入失败。
作为好奇宝宝的你,此时肯定想问:如何才能给静态变量注入系统属性值呢?
答:这就需要使用如下的骚代码了:
@Service
public class UserService {
private static String userName;
@Value("${susan.test.userName}")
public void setUserName(String userName) {
UserService.userName = userName;
}
public String test() {
return userName;
}
}
提供一个静态参数的setter
方法,在该方法上使用@Value注入属性值,并且同时在该方法中给静态变量赋值。
有些细心的朋友可能会发现,@Value注解在这里竟然使用在setUserName方法上了,也就是对应的setter方法,而不是在变量上。
有趣,有趣,这种用法有点高端喔。
不过,通常情况下,我们一般会在pojo实体类上,使用lombok的@Data、@Setter、@Getter等注解,在编译时动态增加setter或getter方法,所以@Value用在方法上的场景其实不多。
上面的内容,都是用的字符串类型的变量进行举例的。其实,@Value注解还支持其他多种类型的系统属性值的注入。
众所周知,在Java中的基本数据类型有4类8种,然我们一起回顾一下:
相对应地提供了8种包装类:
@Value注解对这8中基本类型和相应的包装类,有非常良好的支持,例如:
@Value("${susan.test.a:1}")
private byte a;
@Value("${susan.test.b:100}")
private short b;
@Value("${susan.test.c:3000}")
private int c;
@Value("${susan.test.d:4000000}")
private long d;
@Value("${susan.test.e:5.2}")
private float e;
@Value("${susan.test.f:6.1}")
private double f;
@Value("${susan.test.g:false}")
private boolean g;
@Value("${susan.test.h:h}")
private char h;
@Value("${susan.test.a:1}")
private byte a1;
@Value("${susan.test.b:100}")
private Short b1;
@Value("${susan.test.c:3000}")
private Integer c1;
@Value("${susan.test.d:4000000}")
private Long d1;
@Value("${susan.test.e:5.2}")
private Float e1;
@Value("${susan.test.f:6.1}")
private Double f1;
@Value("${susan.test.g:false}")
private Boolean g1;
@Value("${susan.test.h:h}")
private Character h1;
有了这些常用的数据类型,我们在定义变量类型时,可以非常愉快的玩耍了,不用做额外的转换。
但只用上面的基本类型是不够的,特别是很多需要批量处理数据的场景中。这时候可以使用数组
,它在日常开发中使用的频率很高。
我们在定义数组时可以这样写:
@Value("${susan.test.array:1,2,3,4,5}")
private int[] array;
spring默认使用逗号分隔参数值。
如果用空格分隔,例如:
@Value("${susan.test.array:1 2 3 4 5}")
private int[] array;
spring会自动把空格去掉,导致数据中只有一个值:12345,注意千万别搞错了。
顺便说一下,定义数组的时候,里面还是有挺多门道的。比如上面列子中,我的数据是:1,2,3,4,5。
如果我们把数组定义成:short、int、long、char、string类型,spring是可以正常注入属性值的。
但如果把数组定义成:float、double类型,启动项目时就会直接报错。
小伙伴们,下巴惊掉了没?
按理说,1,2,3,4,5用float、double是能够表示的呀,为什么会报错?
如果使用int的包装类,比如:
@Value("${susan.test.array:1,2,3,4,5}")
private Integer[] array;
启动项目时同样会报上面的异常。
此外,定义数组时一定要注意属性值的类型,必须完全一致才可以,如果出现下面这种情况:
@Value("${susan.test.array:1.0,abc,3,4,5}")
private int[] array;
属性值中包含了1.0和abc,显然都无法将该字符串转换成int。
有了基本类型和数组,的确让我们更加方便了。但对数据的处理,只用数组这一种数据结构是远远不够的,下面给大家介绍一下其他的常用数据结构。
List是数组的变种,它的长度是可变的,而数组的长度是固定的。
我们看看List是如何注入属性值的:
@Value("${susan.test.list}")
private List<String> list;
最关键的是看配置文件:
susan.test.list[0]=10
susan.test.list[1]=11
susan.test.list[2]=12
susan.test.list[3]=13
当你满怀希望的启动项目,准备使用这个功能的时候,却发现竟然报错了。
what?
看来@Value不支持这种直接的List注入。
那么,如何解决这个问题呢?
有人说用@ConfigurationProperties
。
需要定义一个MyConfig类:
@Configuration
@ConfigurationProperties(prefix = "susan.test")
@Data
public class MyConfig {
private List<String> list;
}
然后在调用的地方这样写:
@Service
public class UserService {
@Autowired
private MyConfig myConfig;
public String test() {
System.out.println(myConfig.getList());
return null;
}
}
这种方法确实能够完成List注入。但是,只能说明@ConfigurationProperties注解的强大,跟@Value有半毛钱的关系?
答:没有。
那么,问题来了,用@Value如何实现这个功能呢?
答:使用spring的EL表达式。
List的定义改成:
@Value("#{'${susan.test.list}'.split(',')}")
private List<String> list;
使用#
号加大括号的EL表达式。
然后配置文件改成:
susan.test.list=10,11,12,13
跟定义数组时的配置文件一样。
Set也是一种保存数据的集合,它比较特殊,里面保存的数据不会重复。
我们可以这样定义Set:
@Value("#{'${susan.test.set}'.split(',')}")
private Set<String> set;
配置文件是这样的:
susan.test.set=10,11,12,13
Set跟List的用法极为相似。
但为了证明本节的独特之处,我打算说点新鲜的内容。
如何给List或者Set设置默认值空呢?
有些朋友可能会说:这还不简单,直接在@Value的$表达式后面加个:号不就行了。
具体代码如下:
@Value("#{'${susan.test.set:}'.split(',')}")
private Set<String> set;
结果却跟想象中不太一样: Set集合怎么不是空的,而是包含了一个空字符串的集合?
好吧,那我在:号后加null,总可以了吧? Set集合也不是空的,而是包含了一个"null"字符串的集合。
这也不行,那也不行,该如何是好?
答:使用EL表达式的empty
方法。
具体代码如下:
@Value("#{'${susan.test.set:}'.empty ? null : '${susan.test.set:}'.split(',')}")
private Set<String> set;
运行之后,结果对了:
其实List也有类似的问题,也能使用该方法解决问题。
在这里温馨的提醒一下,该判断的表达式比较复杂,自己手写非常容易写错,建议复制粘贴之后根据实际需求改改。
还有一种比较常用的集合是map,它支持key/value键值对的形式保存数据,并且不会出现相同key的数据。
我们可以这样定义Map:
@Value("#{${susan.test.map}}")
private Map<String, String> map;
配置文件是这样的:
susan.test.map={"name":"苏三", "age":"18"}
这种用法跟上面稍微有一点区别。
设置默认值的代码如下:
@Value("#{'${susan.test.map:}'.empty ? null : '${susan.test.map:}'}")
private Map<String, String> map;
前面我们已经见识过spring EL表达式的用法了,在设置空的默认值时特别有用。
其实,empty
方法只是它很普通的用法,还有更高端的用法,不信我们一起看看。
以前我们注入bean,一般都是用的@Autowired或者@Resource注解。例如:
@Service
public class RoleService {
public String getRoleName() {
return "管理员";
}
}
@Service
public class UserService {
@Autowired
private RoleService roleService;
public String test() {
System.out.println(roleService.getRoleName());
return null;
}
}
但我要告诉你的是@Value注解也可以注入bean,它是这么做的:
@Value("#{roleService}")
private RoleService roleService;
通过这种方式,可以注入id为roleService的bean。
通过EL表达式,@Value注解已经可以注入bean了。既然能够拿到bean实例,接下来,可以再进一步。
在RoleService类中定义了:成员变量、常量、方法、静态方法。
@Service
public class RoleService {
public static final int DEFAULT_AGE = 18;
public int id = 1000;
public String getRoleName() {
return "管理员";
}
public static int getParentId() {
return 2000;
}
}
在调用的地方这样写:
@Service
public class UserService {
@Value("#{roleService.DEFAULT_AGE}")
private int myAge;
@Value("#{roleService.id}")
private int id;
@Value("#{roleService.getRoleName()}")
private String myRoleName;
@Value("#{roleService.getParentId()}")
private String myParentId;
public String test() {
System.out.println(myAge);
System.out.println(id);
System.out.println(myRoleName);
System.out.println(myParentId);
return null;
}
}
在UserService类中通过@Value可以注入:成员变量、常量、方法、静态方法获取到的值,到相应的成员变量中。
一下子有没有豁然开朗的感觉,有了这些,我们可以通过@Value注解,实现更多的功能了,不仅仅限于注入系统属性。
前面的内容都是基于bean的,但有时我们需要调用静态类,比如:Math、xxxUtil等静态工具类的方法,该怎么办呢?
答:用T加括号。
示例1:
@Value("#{T(java.io.File).separator}")
private String path;
可以注入系统的路径分隔符到path中。
示例2:
@Value("#{T(java.lang.Math).random()}")
private double randomValue;
可以注入一个随机数到randomValue中。
通过上面介绍的内容,我们可以获取到绝大多数类的变量和方法的值了。但有了这些值,还不够,我们能不能在EL表达式中加点逻辑?
拼接字符串:
@Value("#{roleService.roleName + '' + roleService.DEFAULT_AGE}")
private String value;
逻辑判断:
@Value("#{roleService.DEFAULT_AGE > 16 and roleService.roleName.equals('苏三')}")
private String operation;
三目运算:
@Value("#{roleService.DEFAULT_AGE > 16 ? roleService.roleName: '苏三' }")
private String realRoleName;
还有很多很多功能,我就不一一列举了。
EL表达式实在太强大了,对这方面如果感兴趣的小伙伴可以找我私聊。
上面巴拉巴拉说了这么多@Value的牛逼用法,归根揭底就是${}
和#{}
的用法。
下面重点说说${}和#{}的区别,这可能是很多小伙伴比较关心的话题。
主要用于获取配置文件中的系统属性值。
例如:
@Value(value = "${susan.test.userName:susan}")
private String userName;
通过:
可以设置默认值。如果在配置文件中找不到susan.test.userName的配置,则注入时用默认值。
如果在配置文件中找不到susan.test.userName的配置,也没有设置默认值,则启动项目时会报错。
主要用于通过spring的EL表达式,获取bean的属性,或者调用bean的某个方法。还有调用类的静态常量和静态方法。
@Value("#{roleService.DEFAULT_AGE}")
private int myAge;
@Value("#{roleService.id}")
private int id;
@Value("#{roleService.getRoleName()}")
private String myRoleName;
@Value("#{T(java.lang.Math).random()}")
private double randomValue;
如果是调用类的静态方法,则需要加T(包名 + 方法名称)。
例如:T(java.lang.Math)。
好了,今天的内容就介绍到这里,希望对你会有所帮助。随便剧透一下,后面的文章会继续介绍:
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/rG665nW67jUGwW8XY4LcuQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。