Spring的@Value注解和SpringBoot yml配置项目实战踩坑总结

知识点

Spring提供了@Value注解,可用于将配置文件或注册中心的属性值动态注入到Bean中。

注:@Value注解在spring-beans包里。

  1. @Value("${...}"):注入获取对应属性文件中定义的属性值;
  2. @Value("#{...}"):表示SpEl表达式通常用来获取Bean的属性;

实例

/**
 * 服务内动态配置
 *
 * @author cdfive
 */
@Slf4j
@Getter
@Setter
@RefreshScope
@Configuration
public class DynamicConfig implements Serializable {

    // 服务内部错误代码
    @Value("${xxx.dynamic.serviceError.code:500}")
    private Integer serviceErrorCode;

    // 服务内部错误消息
    @Value("${xxx.dynamic.serviceError.msg:服务繁忙,请稍候再试}")
    private String serviceErrorMsg;
    
    // RPC日志是否启用
    @Value("${xxx.dynamic.rpcLogEnable:true}")
    private boolean rpcLogEnable;
    
    // 业务内部线程池核心线程数
    @Value("${xxx.dynamic.bizInteralThreadPool.coreSize:4}")
    private int bizInteralThreadPoolCoreSize;

    ...

    // 跟踪的商品编码集合
    @Value("${xxx.dynamic.trace.productCode:#{null}}")
    private String traceProductCode;

通过@Value("${xxx}")Bean里的字段映射配置文件的值,如application.yml

xxx:
  dynamic:
    serviceError:
      code: 500
      msg: 系统维护中,请稍候再试
    trace:
      productCode: 100000001

注意点1---默认值

如果配置文件里没有xxx的配置,则@Value("${xxx}")需要指定默认值,否则启动应用报错。

例如:

@Value("${error.code.msg}")
private Integer errorCodeMsg;

应用启动报错:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'error.code.msg' in value "${error.code.msg}"
	at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180)
	at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
	at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239)
	at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)

通过:分隔后面加上默认值。
例如:

@Value("${error.code.success:200}")
private Integer errorCodeSuccess;

如果外部没有配置codeSuccess,则它的默认值为200。

当属性为IntegerStringBoolean对象类型,如果希望外部没有配置时,默认为null,则在:后加上#{null}
例如:

@Value("${error.code.msg:#{null}}")
private String errorCodeMsg;

如果只写:String类型默认值为空字符串"";
例如:

@Value("${error.code.msg:}")
private String errorCodeMsg;

注:对于IntegerLong类型,xxx:#{null}xxx:效果相同,默认值为null

注意点2---集合类型

List,Set类型,集合每一项值通过英文逗号,分隔

@Value("${global.config.nums}")
private List<Integer> nums;

@Value("${global.config.items}")
private Set<String> items;
global:
  config:
    nums: 1,2,3
    items: aa,bb,cc

如果不想在yml配置,可在Bean里指定默认值:

@Value("${global.config.nums:1,2,3}")
private List<Integer> nums;

@Value("${global.config.items:aa,bb,cc}")
private Set<String> items;

如果想Bean的集合字段默认值为null,等有需要时再去yml里配置,则加上:#{null}即可。

@Value("${global.config.nums:#{null}}")
private List<Integer> nums;

@Value("${global.config.items:#{null}}")
private Set<String> items;

如果想Bean的集合字段默认值为空集合,等有需要时再去yml里配置,则加上:即可。

@Value("${global.config.nums:}")
private List<Integer> nums;

@Value("${global.config.items:}")
private Set<String> items;

默认空集合可不用null判断,避免NPE空指针异常。

注意点3---SpEL表达式

使用split方法,英文逗号分隔

@Value("#{'${xxx.dynamic.codes:111,222}'.split(',')}")
private List<String> codes;

注意这种方式:
1.如果不填默认值,且外部配置没有:

@Value("#{'${xxx.dynamic.codes}'.split(',')}")
private List<String> codes;

则启动报错Could not resolve placeholder

2.默认值里不能用#{null}

@Value("#{'${xxx.dynamic.codes:#{null}}'.split(',')}")
private List<String> codes;

整个会被当作字符串解析,解析结果为列表里只有1个元素,值为#{null}

3.写:也不行

@Value("#{'${xxx.dynamic.codes:#{null}}'.split(',')}")
private List<String> codes;

解析结果为列表里只有1个元素,值为空字符串""

如果想代码里不填默认值,且允许外部不配置,此时列表为空列表:
可考虑自定义方法decodeList

@SuppressWarnings("rawtypes")
public static List<String> decodeList(String value) {
    if (StringUtils.isBlank(value)) {
        return Collections.emptyList();
    }

    return Splitter.on(",").omitEmptyStrings().splitToList(value).stream().collect(Collectors.toList());
}

通过#{T(包名.类名).方法名('${...}')}使用自定义方法:

@Value("#{T(xxx.DynamicConfig).decodeList('${xxx.dynamic.codes}')}")
private List<String> codes;

Map类型,自定义方法decodeMap

@SuppressWarnings("rawtypes")
public static Map decodeMap(String value) {
    if (StringUtils.isBlank(value)) {
        return Collections.emptyMap();
    }

    return JsonUtils.json2Obj(value, Map.class);
}

属性设置:

@Value("#{T(xxx.DynamicConfig).decodeMap('${xxx.dynamic.channelTypes}')}")
private Map<String, List<String>> channelTypes;

外部配置:

xxx:
  dynamic:
    channelTypes: '{"aa":["xx","yy"],"bb":["zz"]}'

补充1

SpringBoot在Spring的基础上,提供了更多的注解用于配置管理,如:@ConfigurationPropertiesEnableConfigurationProperties等。

例:

@Data
@ConfigurationProperties(prefix = "xxx.config")
public class XxxProperties {

    private Boolean enable;
    
    private String code;
   
    ...
}
@Slf4j
@Configuration
@EnableConfigurationProperties(XxxProperties.class)
public class XxxAutoConfig {
    ...
}

其中:

  • @ConfigurationProperties(prefix = "xxx.config")把类的属性与yml配置文件里配置进行绑定;
  • @EnableConfigurationProperties(XxxProperties.class),将XxxProperties.class添加至Spring容器;
  • 也可不使用@EnableConfigurationProperties,那么XxxProperties.class需要通过其他方式添加至Spring容器,如标记注解@Component

补充2

外部配置,如项目里的application.yml文件、或者Nacos配置中心里yml配置,如果属性类型为String,属性值为0开头,则属性值需要加上双引号

配置类:

@Value("${xxx.config.codes}")
private String code;

yml文件:

xxx:
  config:
    code: 048

项目启动,发现code值不是期望的048,而是48.0

修改yml文件:

xxx:
  config:
    code: "048"

再次重启项目,code值为预期的048

posted @ 2024-07-06 21:56  cdfive  阅读(2180)  评论(0)    收藏  举报