【Java】注解@Value你真的会⽤么
前⾔
对于从事java开发⼯作的⼩伙伴来说,spring框架肯定再熟悉不过了。spring给开发者提供了⾮常丰富的api,满⾜我们⽇常的⼯作需求。如果想要创建bean实例,可以使⽤@Controller、@Service、@Repository、@Component等注解。
如果想要依赖注⼊某个对象,可以使⽤@Autowired和@Resource注解。
如果想要开启事务,可以使⽤@Transactional注解。
如果想要动态读取配置⽂件中的某个系统属性,可以使⽤@Value注解。
等等,还有很多。。。
今天咱们重点聊聊@Value注解,因为它是⼀个⾮常有⽤,但极其容易被忽视的注解,绝⼤多数⼈可能只⽤过它的⼀部分功能,这是⼀件⾮常遗憾的事情。
1.⼀个例⼦
假如在UserService类中,需要注⼊系统属性到userName变量中。通常情况下,我们会写出如下的代码:
@Service
public class UserService {
@Value("${st.userName}")
private String userName;
public String test(){
System.out.println(userName);
return userName;
}
}
通过@Value注解指定系统属性的名称st.userName,该名称需要使⽤${}包起来。
读取配置文件失败
这样spring就会⾃动的帮我们把对应的系统属性值,注⼊到userName变量中。
不过,上⾯功能的重点是要在applicationContext.properties⽂件(简称:配置⽂件)中配置同名的系统属性:
#张三
杭州西湖十景
那么,名称真的必须完全相同吗?
2. 关于属性名
这时候,有些朋友可能会说:在@ConfigurationProperties配置类中,定义的参数名可以跟配置⽂件中的系统属性名不同。
⽐如,在配置类MyConfig类中定义的参数名是userName:
@Configuration
@ConfigurationProperties(prefix ="st")
@Data
public class MyConfig {
private String userName;
}
⽽配置⽂件中配置的系统属性名是:
类中⽤的userName,⽽配置⽂件中⽤的user-name,不⼀样。但测试之后,发现该功能能够正常运⾏。
配置⽂件中的系统属性名⽤ 驼峰标识 或 ⼩写字母加中划线的组合,spring都能到配置类中的属性名userName进⾏赋值。
由此可见,配置⽂件中的系统属性名,可以跟配置类中的属性名不⼀样。不过,有个前提,前缀st必须相同。
那么,@Value注解中定义的系统属性名也可以不⼀样吗?
答案:不能。如果不⼀样,启动项⽬时会直接报错。
此外,如果只在@Value注解中指定了系统属性名,但实际在配置⽂件中没有配置它,也会报跟上⾯⼀样的错。
所以,@Value注解中指定的系统属性名,必须跟配置⽂件中的相同。
3. 乱码问题
不知道细⼼的⼩伙伴们有没有发现,我配置的属性值:张三,其实是转义过的。
为什么要做这个转义?
假如在配置⽂件中配置中⽂的张三:
最后获取数据时,你会发现userName竟然出现了乱码:
å¼ä¸‰
what?
为什么会出现乱码?
答:在springboot的CharacterReader类中,默认的编码格式是ISO-8859-1,该类负责.properties⽂件中系统属性的读取。如果系统属性包含中⽂字符,就会出现乱码。
那么,如何解决乱码问题呢?
⽬前主要有如下三种⽅案:
1. ⼿动将ISO-8859-1格式的属性值,转换成UTF-8格式。
2. 设置encoding参数,不过这个只对@PropertySource注解有⽤。
3. 将中⽂字符⽤unicode编码转义。
显然@Value不⽀持encoding参数,所以⽅案2不⾏。
假如使⽤⽅案1,具体实现代码如下:
@Service
public class UserService {
@Value(value ="${st.userName}")
private String userName;
public String test(){
String userName1 =new Bytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
System.out.println();
return userName1;
}
}
确实可以解决乱码问题。
宋佳 张黎但如果项⽬中包含⼤量中⽂系统属性值,每次都需要加这样⼀段特殊转换代码。出现⼤量重复代码,有没有觉得有点恶⼼?反转我被恶⼼到了。
那么,如何解决代码重复问题呢?
答:将属性值的中⽂内容转换成unicode。
类似于这样的:
这种⽅式同样能解决乱码问题,不会出现恶⼼的重复代码。但需要做⼀点额外的转换⼯作,不过这个转换⾮常容易,因为有现成的在线转换⼯具。
在这⾥顺便告诉你⼀个⼩秘密:如果你使⽤的是.yml或.yaml格式的配置⽂件,并不会出现中⽂乱码问题。
这⼜是为什么?
因为.yml或.yaml格式的配置⽂件,最终会使⽤UnicodeReader类进⾏解析,它的init⽅法中,⾸先读取BOM⽂件头信息,如果头信息中有UTF8、UTF16BE、UTF16LE,就采⽤对应的编码,如果没有,则采⽤默认UTF8编码。
需要注意的是:乱码问题⼀般出现在本地环境,因为本地直接读取的.properties配置⽂件。在dev、test、⽣产等环境,如果从zookeeper、apollo、nacos等配置中⼼中获取系统参数值,⾛的是另外的逻辑,并不会出现乱码问题。
4.默认值
有时候,默认值是我们⾮常头疼的问题。
为什么这样说呢?
因为很多时候使⽤java的默认值,并不能满⾜我们的⽇常⼯作需求。
⽐如有这样⼀个需求:如果配置了系统属性,userName就⽤配置的属性值。如果没有配置,则userName⽤默认值susan。
有些朋友可能认为可以这样做:
@Value(value ="${st.userName}")
private String userName ="susan";
在定义参数时直接给个默认值,但如果仔细想想这招是⾏不通的的。因为设置userName默认值的时机,⽐@Value注解依赖注⼊属性值要早,也就是说userName初始化好了默认值,后⾯还是会被覆盖。
那么,到底该如何设置默认值呢?
答:使⽤:。
例如:
津津有味@Value(value ="${st.userName:susan}")
private String userName;
在需要设置默认值的系统属性名后,加:符号。紧接着,在:右边设置默认值。
建议⼤家平时在使⽤@Value时,尽量都设置⼀个默认值。如果不需要默认值,宁可设置⼀个空。⽐如:
@Value(value ="${st.userName:}")
private String userName;
为什么这么说?
假如有这种场景:在business层中包含了UserService类,business层被api服务和job服务都引⽤了。但UserService类中@Value的userName只在api服务中有⽤,在job服务中根本⽤不到该属性。
对于job服务来说,如果不在.properties⽂件中配置同名的系统属性,则服务启动时就会报错。
这个坑,我之前踩过多次。所以,建议⼤家,使⽤@Value注解时,最好给参数设置⼀个默认值,以防⽌出现类似的问题。
5. static变量
峰可以组什么词语
前⾯我们已经见识过,如何使⽤@Value注解,给类的成员变量注⼊系统属性值。
那么,问题来了,静态变量可以⾃动注⼊系统属性值不?
我们⼀起看看,假如将上⾯的userName定义成static的:
@Value("${st.userName}")
优酷弹幕关闭中怎么开启private static String userName;
程序可以正常启动,但是获取到userName的值却是null。
由此可见,被static修饰的变量通过@Value会注⼊失败。
作为好奇宝宝的你,此时肯定想问:如何才能给静态变量注⼊系统属性值呢?
答:这就需要使⽤如下的骚代码了: