目录

SpringBoot读取yml/properties的几种方法

前言

由于一些众所周知且不可抗拒的力量,个人的Spring底子很差,Boot和MVC部分还可以。特别是学习了设计模式,认为开发一个软件时Spring这种解耦合的框架对于软件开发是非常重要的。甚至决定了一个项目的整个生命周期。

正好在整理笔记的时候看到了之前检索的SpringBoot读取yml/properties知识。由于很零碎,故再次检索并尝试将能想到的情况整合到一个项目中。

方法

1@Component+@Value+@Resource

数据

yml

1
2
3
4
5
6
7
  first:
    id: 1
    name: zhangsan1
    age: 771
    array: 1,6,11
    list: 1,6,11
    map: '{ "key1": "value1","num": "111" }'

properties

1
2
3
4
5
6
config.first.id=1
config.first.name=zhangsan1
config.first.age=771
config.first.array=1,6,11
config.first.list=1,6,11
config.first.map={"key1":"value1","num":"111"}

实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
//Configuration
@Data
public class First {
    @Value("${config.first.id}")
    private String id;
    @Value("${config.first.name}")
    private String name;
    @Value("${config.first.age}")
    private Integer age;
    @Value("${config.first.array}")
    private int[] array;

    @Value("${config.first.array1:}")
    private int[] array1;

    @Value("#{'${config.first.list:}'.split(',')}")
    private List list;

    @Value("#{${config.first.map:}}")
    private Map map;

}

调用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class ConfigTestController {
    @Resource
//    @Autowired
    private First first;

    @GetMapping("/first")
    public String getFirst() {
        String single = "first.id=" + first.getId() + ";first.age=" + first.getAge() + ";first.name=" + first.getName()
                + ";<br/>first.array=";
        for (int mi : first.getArray()) {
            single += (mi + ",");
        }
        single += ";";
        if (first.getArray1().length == 0) single += "<br/>first.getArray1()key不存在,长度为0<br/>";
        single+=first.getList();
        single+="<br/>";
        single+=first.getMap();
        return single;
    }

注意

  • 基本数据类型的Collection类型数据需要在外面加上#{}
  • 后面加上冒号是可以在配置文件不存在时,设置默认值,Collection类型数据长度为0 的对象
  • 这种方法只能返回基本类型和对应的Collection类型,不能直接返回first对象,会报错
  • 大佬自己想办法实现了复杂类型的读取,我没实验,看这
  • 解析Collection类型时可以用CollectionUtils.isEmpty对对象进行是否成功输入进行判断,或者用这样的el表达式
1
2
@Value("#{'${test.list:}'.empty ? null : '${test.list:}'.split(',')}")  
private List testList;  
  • 解析map在配置文件中要是直接写map结构
1
2
3
test:  
  map1: '{"name": "zhangsan", "sex": "male"}'  
  map2: '{"math": "90", "english": "85"}'  

在程序中,利用 EL 表达式注入:

1
2
3
4
@Value("#{${test.map1}}")  
private Map map1;  
@Value("#{${test.map2}}")  
private Map map2;  

注意,使用这种方式,必须得在配置文件中配置该 key 及其 value。我在网上找了许多资料,都没找到利用 EL 表达式支持不配置 key/value 的写法。

如果你真的很需要这个功能,就得自己写解析方法了,这里以使用 fastjson 进行解析为例:

(1) 自定义解析方法

1
2
3
4
5
6
7
public class MapDecoder {  
    public static Map decodeMap(String value) {  
        try {  return JSONObject.parseObject(value, new TypeReference>(){});  
        } catch (Exception e) {  return null;  
        }  
    }  
}  

(2) 在程序中指定解析方法

1
2
3
4
@Value("#{T(com.github.jitwxs.demo.MapDecoder).decodeMap('${test.map1:}')}")  
private Map map1;  
@Value("#{T(com.github.jitwxs.demo.MapDecoder).decodeMap('${test.map2:}')}")  
private Map map2;  
  • 注意的是 @Value 注解不能和 @AllArgsConstructor 注解同时使用,否则会报错
1
Consider defining a bean of type 'java.lang.String' in your configuration  

这种做法唯一不优雅的地方就是,这样写出来的 @Value 的内容都很长,既不美观,也不容易阅读。

优缺点

  • 可以看见,单值十分的不方便,最大的问题是配置和代码高耦合了,增加一个配置,还需要对配置类做增减改动,这样就能够直接使用了,就是这么的简单方便,如果你想要支持不配置 key 程序也能正常运行的话,给它们加上默认值即可
  • 不需要写配置类,使用逗号分割,一行配置,即可完成多个数值的注入,配置文件更加精简,但是业务代码中数组使用很少,基本需要将其转换为 List,去做 contains、foreach 等操作。

2@Component+@ConfigurationProperties+@Resource

数据

yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
second:
    id: 2
    name: zhangsan2
    age: 772
    array: 2,7,12
    list: 2,7,12
    map:
      key2: value2
      num: 222
    users:
      -
          id: 11
          name: lilei
          age: 10
      -
          id: 2
          name: zhaosi
          age: 65
      -
          id: 3
          name: wangwu
          age: 23

properties

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
config.second.id=2
config.second.name=zhangsan2
config.second.age=772
config.second.array=2,7,12
config.second.list=2,7,12
#config.second.map={"key2":"value2","num":"222"}
config.second.map.key2=value2
config.second.map[num]=222

config.second.users[0].id=11
config.second.users[0].name=lilei
config.second.users[0].age=10

config.second.users[1].id=2
config.second.users[1].name=zhaosi
config.second.users[1].age=65

config.second.users[2].id=3
config.second.users[2].name=wangwu
config.second.users[2].age=23

实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
@Data
@ConfigurationProperties(prefix = "config.second")
public class Second {
    private String id;
    private String name;
    private Integer age;
    private int[] array;
    //    private int[] array1;
    private List list;
    private Map map;

    public List<User> users;

    @Data
    public static class User {
        private Long id;
        private String name;
        private Integer age;
    }
}

调用方法

1
2
3
4
5
6
7
8
    @Resource
//    @Autowired
    private Second second;

    @GetMapping("/second")
    public Second getSecond() {
        return second;
    }

注意

  • 不允许有不存在的字段,需要和配置文件一一对应
  • 除了基本数据类型还允许配置自定义对象及自定义对象的Collection类型

3@EnableConfigurationProperties+@ConfigurationProperties+@Resource

数据

yml 同Second

properties 同Second

实体类

同Second,去掉@Component注解

调用方法

需要在注入的类添加@EnableConfigurationProperties(Third.class)注解,

1
2
@EnableConfigurationProperties(Third.class)
public class ConfigTestController {/*调用逻辑同Second*/}

注意

同Second

4@Configuration+@ConfigurationProperties+@Bean

数据

yml 同Second

**properties **同Second

实体类

同Second,去掉@Component注解

调用方法

将@Resource换为@Bean

需要在注入的类添加@Configuration注解

注意

  • 同Second
  • 调用方法的@Configuration不能换为@Component;不添加@Configuration或添加了@Component会报错
1
Method annotated with @Bean is called directly. Use dependency injection instead.

5@Resource+Environment

数据

**yml **同First

**properties **同First

实体类

不需要

调用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    @Resource
    private Environment env;
    public String getFifth() {
        String single = "fifth.id=" + env.getProperty("config.fifth.id") +
                ";fifth.age=" + env.getProperty("config.fifth.age") +
                ";fifth.name=" + env.getProperty("config.fifth.name")
                + ";<br/>fifth.array="+env.getProperty("config.fifth.array")
                + ";<br/>fifth.list="+env.getProperty("config.fifth.list")
                + ";<br/>fifth.map="+env.getProperty("config.fifth.map");
        return single;
    }
}

注意

  • 只能读取为String,其他结构需要自己对字符串进行处理转化其他类型数据,因为很底层,所以用的很少
  • 所有的@Resource都可以用@Autowired替换,区别看这

总结

  • 文中的@Configuration 可以替换为@Component运行结果是一样的,但是两者是有不同的,@Configuration会为配置类生成CGLIB代理Class,@Component不会;看了篇博文有解释
  • @ConfigurationProperties详解的博文
  • 在yml中: 字符串默认不用加上单引号或者双引号; “":双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思 name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi ‘':单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据 name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi
  • 不添加@PropertySource注解会访问默认的application.yml或者properties