springboot如何快速读取配置项?

2022/06/02 springboot专题 共 32145 字,约 92 分钟
闷骚的程序员

1. springboot工程如何读取配置文件的配置项到实体对象中呢?

springboot工程一般都会默认有application.yml或者application.properties文件,我们会把整个工程中所用到的一些配置项以“key-value”的形式配置在这些配置文件中。
但是在项目启动后,如何读取这些配置文件中的指定配置项呢?
当然,可以自己编写解析类专门来解析这些文件,然后挨个读取其中的key-value。
但是springboot本身有更简洁的方式,它底层通过钩子接口实现的拦截器,会在容器启动时检查某些注解,然后在生成这些注解标注的bean对象时,通过反射将配置文件中的key-value值填充到这些bean属性中。
具体来说,有4种方式:
(1)直接在需要使用的类中注入Environment对象。
(2)在要使用的类的属性字段上使用@Value注解来标注需要获取的配置文件中的key。
(3)专门定义一个配置类,标注@ConfigurationProperties注解,这样可以统一将配置文件中的所有配置项统一映射到同一个配置类中。
(4)专门定义一个配置类,标注@PropertySource注解,这个注解可以指定目标配置文件,即如果我们要读取的配置文件不是springboot默认的application.yml或者application.properties时,可以通过这个注解来获取配置项。这个注解需要结合@Value或者@ConfigurationProperties注解一起使用,你可以这样理解,这个注解只是用来指定我们自定义的外部配置文件的而已。

这几种方式做一个简单的图解:

2. 通过Environment获取springboot配置项

2.1 先搞配置项

application.properties

# 若没有引入 spring-boot-starter-web 依赖,则需要将web环境关闭,才能正常启动,否则会报找不到servlet容器异常

# 若引入 spring-boot-starter-web 依赖,且没有关闭web环境,则应用启动时除了开启微服务的端口外,也会默认启动springboot的内嵌Tomcat容器(会另外开启一个端口)

#profiles多环境配置    

#实际开发中,一套应用存在于多个环境,开发环境、测试环境、生产环境等,各个环境数据库地址等配置信息都不同。通过profile来激活不同环境对应的配置文件,从而解决多环境下配置信息不一致的问题。

#可以定义多个配置文件,每个配置文件对应一个环境,格式为 application-环境.properties,一般遵守规则如下:

#(1)、application.properties:通用配置文件,不区分环境。
   
#(2)、application-dev.properties:开发环境的配置文件命名。   

#(3)、application-test.properties:测试环境的配置文件命名。   

#(4)、application-prod.properties:生产环境的配置文件命名。  

#通过修改application.properties文件中,spring.profiles.active属性的值,来切换到对应环境的配置上,注意:spring.profiles.active取值为对应环境配置文件命名的后缀。   

#比如,application-dev.properties就取值为dev,application-test.properties取值为test,application-prod.properties取值为prod。   

#在部署时,可以通过 java -jar xxx.jar --spring.profiles.active=dev 来激活对应的环境配置。   

spring.profiles.active=dev

#default is /,应用的上下文路径。此处配置的路径作为该应用所有访问url的一部分,如果是默认的/,则url访问不用加,如果是显式配置,则需要加。   

# 如果是默认的/,则访问:http://127.0.0.1:8088/zhaoerhu/hello   

# 如果是/myspringbootservice,则访问:http://127.0.0.1:8088/myspringbootservice/zhaoerhu/hello   

#server.servlet-path=/myspringbootservice   

server.servlet-path=/

#出现错误时,直接抛出异常。默认为false。要使用统一异常处理,请配置该项。   

spring.mvc.throw-exception-if-no-handler-found=true

#不要为工程中的资源文件建立映射。要使用统一异常处理,请配置该项。   

spring.resources.add-mappings=false

#static file default directory location
spring.resources.static-locations=classpath:/public/
#spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/   

将一些应用配置写在dev配置文件application-dev.properties中:

# 设置springboot的日志级别   

logging.level.org.springframework=DEBUG

# 自定义配置,通过代码进行读取,进行测试。   

zeh.test.name=Eric Zhao
zeh.test.age=22
zeh.test.flag=false
zeh.test.staticField=STATIC
zeh.test.constant=CONSTANT
# 搞一些名称中有特殊横杠和特殊大小写的自定义配置   

zeh.test.table-nAme=Student
zeh.test.table-Name=Person
# 搞一些嵌套属性,比如list,map和普通的class等。   

# map结构的配置项   

zeh.test.personMap.personName=zhangsan
zeh.test.personMap.personHobby=tiaowu
# []也能指定map的结构   

zeh.test.studentMap[studentSex]=man
zeh.test.studentMap[studentUrl]=www.zeh.com

# list结构   

zeh.test.myList[0]=apple0
zeh.test.myList[1]=apple1
zeh.test.myList[2]=apple2
# 逗号分隔也能指定list结构   

zeh.test.yourList=phone0.phone1,phone2

#spring boot的默认web启动端口,default is 8080,如果启动多个spring boot工程,则修改此处的端口配置,指定其他端口。   

server.port=8088

# 测试@ConfigurationProperties注解默认读取配置项批量注入到实体字段中,对应ClazzConfig实体   

spring.config.clazz.testField=TEST-001
  
# 数据源   

# 设置使用dbcp数据源   

spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl
spring.datasource.username=oracle
spring.datasource.password=oracle123
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver

# mybatis mapper   

mybatis.default.mapper-locations=classpath*:mybatis/mapper/**/*.xml

我们要获取的配置项大概如下:

# 自定义配置,通过代码进行读取,进行测试。   

zeh.test.name=Eric Zhao
zeh.test.age=22
zeh.test.flag=false
zeh.test.staticField=STATIC
zeh.test.constant=CONSTANT

# 搞一些名称中有特殊横杠和特殊大小写的自定义配置   

zeh.test.table-nAme=Student
zeh.test.table-Name=Person

# 搞一些嵌套属性,比如list,map和普通的class等。   

# map结构的配置项   

zeh.test.personMap.personName=zhangsan
zeh.test.personMap.personHobby=tiaowu
# []也能指定map的结构    

zeh.test.studentMap[studentSex]=man
zeh.test.studentMap[studentUrl]=www.zeh.com

# list结构    

zeh.test.myList[0]=apple0
zeh.test.myList[1]=apple1
zeh.test.myList[2]=apple2

# 逗号分隔也能指定list结构

zeh.test.yourList=phone0.phone1,phone2

#spring boot的默认web启动端口,default is 8080,如果启动多个spring boot工程,则修改此处的端口配置,指定其他端口。   

server.port=8088

注意,这两个配置文件在后面的案例中将不再重复编写。

2.2 搞一个配置类

code04controller.config.EnvironmentConfig

package code04controller.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

// 通过在当前类中注入Environment对象来获取springboot的配置项
@Component //当前类必须交给spring管理
public class EnvironmentConfig {

    // 读取配置方式1:注入 Environment 对象,通过Environment对象的getProperty()方法获取springboot配置文件中指定的配置项。
    @Autowired
    private Environment environment;

    // springboot的配置信息通常放在application.properties文件,或者在主文件中指向的其他环境配置文件
    // 比如application-dev.properties中。
    // 此处通过 Environment 对象的getProperty()方法获取配置文件中指定属性的配置项。
    public String getName() {
        return environment.getProperty("zeh.test.name");
    }

    public int getAge() {
        return Integer.parseInt(environment.getProperty("zeh.test.age"));
    }

    public int getPort() {
        return Integer.parseInt(environment.getProperty("server.port"));
    }

    public boolean getFlag() {
        return Boolean.parseBoolean(environment.getProperty("zeh.test.flag"));
    }

    // environment 对于 map 结构的配置项都无法成功获取
    public Map<String,String> getPersonMap(){
        return environment.getProperty("zeh.test.personMap",Map.class);
    }

    // environment 对于 map 结构的配置项都无法成功获取
    public Map<String,String> getStudentMap(){
        return environment.getProperty("zeh.test.studentMap",Map.class);
    }

    // environment 对于 zeh.test.myList[0]=apple0 这种list无法成功获取
    public List<String> getMyList() {
        return environment.getProperty("zeh.test.myList", List.class);
    }

    // environment 对于 zeh.test.yourList=phone0.phone1,phone2 这种配置的list,可以成功转换为list对象。
    public List<String> getYourList() {
        return environment.getProperty("zeh.test.yourList", List.class);
    }
}

注意几点:
(1)spring容器在启动时内部已经通过编码的方式实例化好了Environment的实例放在了spring容器中,因此,外部可以直接注入该bean。
(2)当前的配置类必须交给spring管理才行,使用@Controller,@Service,@Component,@Bean等的方式标注都行。
(3)Environment对象的方式不仅仅能读取配置文件的key值,也可以读取一切外部资源进来的key值,比如JVM参数,环境变量等。当时当它读取配置文件时,只能读取springboot默认的配置文件application,yml或者application,properties,或者是其指定profile后的文件,比如此案例中的application-dev.properties。
(4)Environment对象对于map结构的配置项都无法成功映射成map。
(5)Environment对象对于“zeh.test.myList[0]=apple0”这种配置方式的list配置无法成功映射为list。
(6)Environment对象对于“zeh.test.yourList=phone0.phone1,phone2”这种方式的list配置可以成功映射到list对象中。

2.3 搞一个controller来测试

code04controller.controller.Demo03GetConfigController

package code04controller.controller;

import code04controller.config.EnvironmentConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 功能描述:测试springboot读取文件的各种方式
 *
 * @ClassName Demo03GetConfigController
 * @Author zhaoerhu
 * @Date 2023/10/6 21:59
 */
@RestController
@RequestMapping("demo03GetConfigController")
public class Demo03GetConfigController {

    // 向当前controller中注入spring管理的这个配置bean
    @Autowired
    private EnvironmentConfig environmentConfig;

    // 测试通过Environment对象获取springboot配置项
    @GetMapping("getConfigByEnvironment")
    public String getConfigByEnvironment() {
        System.out.println("进入 getConfigByEnvironment() 方法...");
        // 通过environment对象获取配置文件中的name
        String name = environmentConfig.getName();
        System.out.println("environment对象方式 获取的配置 zeh.test.name 为:" + name);
        int age = environmentConfig.getAge();
        System.out.println("environment对象方式 获取的配置 zeh.test.age 为:" + age);
        int port = environmentConfig.getPort();
        System.out.println("environment对象方式 获取的配置 server.port 为:" + port);
        boolean flag = environmentConfig.getFlag();
        System.out.println("environment对象方式 获取的配置 zeh.test.flag 为:" + flag);

        // 获取map
        Map<String,String> personMap = environmentConfig.getPersonMap();
        System.out.println("environment对象方式 获取的配置 zeh.test.personMap 为:" + personMap);
        Map<String,String> studentMap = environmentConfig.getStudentMap();
        System.out.println("environment对象方式 获取的配置 zeh.test.studentMap 为:" + studentMap);

        // 获取list
        List<String> myList = environmentConfig.getMyList();
        System.out.println("environment对象方式 获取的配置 zeh.test.myList 为:" + myList);
        List<String> yourList = environmentConfig.getYourList();
        System.out.println("environment对象方式 获取的配置 zeh.test.yourList 为:" + yourList);
        return "getConfigByEnvironment";
    }

}

注意:当前controller必须交给spring管理,通过@RestController标注;必须注入配置类EnvironmentConfig,通过它得到它获取到的配置项。

2.4 postman访问测试

postman中访问连接:
http://localhost:8088/demo03GetConfigController/getConfigByEnvironment
控制台日志如下:

进入 getConfigByEnvironment() 方法...
environment对象方式 获取的配置 zeh.test.name 为:Eric Zhao
environment对象方式 获取的配置 zeh.test.age 为:22
environment对象方式 获取的配置 server.port 为:8088
environment对象方式 获取的配置 zeh.test.flag 为:false
environment对象方式 获取的配置 zeh.test.personMap 为:null
environment对象方式 获取的配置 zeh.test.studentMap 为:null
environment对象方式 获取的配置 zeh.test.myList 为:null
environment对象方式 获取的配置 zeh.test.yourList 为:[phone0.phone1, phone2]

从日志可以看出:
environment对象成功获取string类型的value;
成功获取Int类型的value;
成功获取boolean类型的value;
对于map类型的value无法获取;
对于配置方式为“zeh.test.myList[0]=apple0”类型的list无法获取;
对于配置方式为“zeh.test.yourList=phone0.phone1,phone2”类型的list可以成功获取。

3. 通过@Value获取springboot配置项

@Value用来读取application.yml或者application.properties配置文件中属性的值,当然也包括主配置中指定的profile中的值,比如application-dev.yml、application-prod.properties等。

3.1 搞一个配置类

package code04controller.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

// 通过在当前类的属性上标注@Value注解来获取springboot配置项
@Component
public class ValueConfig {

    //读取配置方式2:通过@Value注入配置文件中的配置项到需要接收的成员变量上,则可以直接获取到配置项。
    @Value("${zeh.test.name} , wo shi ni gege!")
    private String name;

    @Value("${zeh.test.age}")
    private int age;

    @Value("${server.port}")
    private int port;

    // 不论配置项key名称中有啥特殊的和大小写,此处@Value获取的必须和其名称完全一样,不能内部做任何改变,否则启动报错,找不到指定的配置项
    @Value("${zeh.test.table-nAme}")
    private String tableName;

    // 字段由@Value显式标注其和配置项的映射关系,属性字段的命名就可以任意命名。
    @Value("${zeh.test.flag}")
    private boolean myFlag;

    // @Value无法向静态成员注入配置项
    @Value("${zeh.test.staticField}")
    private static String staticFieldStr;

    // @Value向常量成员注入配置项的前提是该常量的初始化值必须是null,不能是其他字面量。
    // 一旦该常量初始化为其他字面量,则@Value服务成功注入配置项的值。
    @Value("${zeh.test.constant}")
    private final String constantStr = null;

    // @Value可以直接获取 zeh.test.yourList=phone0.phone1,phone2 类型的list,其他的比如map和其他类型list只要Key和@Value指定的不匹配就会启动报错。
    @Value("${zeh.test.yourList}")
    private List<String> yourList;

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public int getPort() {
        return this.port;
    }

    public String getTableName() {
        return this.tableName;
    }

    public boolean getMyFlag() {
        return this.myFlag;
    }

    public String getStaticFieldStr() {
        return staticFieldStr;
    }

    public String getConstantStr() {
        return constantStr;
    }

    public List<String> getYourList() {
        return this.yourList;
    }

}

注意:
(1)当前配置类必须交给spring管理;比如使用 @Component,@Service,@Controller,@Repository,@Bean,或者 @Configuration,当然一般都使用 @Component。
(2)当前配置类的属性上,必须使用@Value注解标注,@Value注解只有一个String类型的参数,参数的格式必须是”${}”类型,”{}”中必须通过链式写法指定要读取的配置文件中的key。如果在”{}”后面还有字符换,则会将读取到的配置项值和后面的串一同拼接赋值给当前的字段。
(3)当前类对应字段的类型必须和配置文件中要读取的配置项的实际类型先匹配,否则将报类型转换错误。
(4)使用@Value注解注入外部资源文件的value时不需要编写对应的setter和getter方法。
(5)@Value无法将配置项的值注入到一个静态成员变量上。
(6)@Value可以将配置项的值注入到一个常量成员上,前提是该成员常量的初始化值必须是null而不能是其他显式指定的值,如果是其他显式指定的值,配置项将注入失败。
(7)@Value对于配置方式为“zeh.test.yourList=phone0.phone1,phone2”的list可以成功获取,对于其他方式配置的map和list无法成功获取。总而言之,@Value()中要获取目标配置项,“{}”中引用的配置key名称必须在文件中存在,否则容器启动报错。

3.2 在上面的controller中增加代码

code04controller.controller.Demo03GetConfigController

package code04controller.controller;

import code04controller.config.ConfigurationPropertiesConfig;
import code04controller.config.EnvironmentConfig;
import code04controller.config.ValueConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

// 测试springboot读取文件的各种方式
@RestController
@RequestMapping("demo03GetConfigController")
public class Demo03GetConfigController {


    // 向当前controller中注册spring管理的ValueConfig
    @Autowired
    private ValueConfig valueConfig;

    // 测试通过@Value注解获取springboot配置项
    @GetMapping("getConfigByValue")
    public String getConfigByValue() {
        System.out.println("进入 getConfigByValue() 方法...");
        String name = valueConfig.getName();
        System.out.println("@Value注解方式 获取的配置 zeh.test.name 为:" + name);
        int age = valueConfig.getAge();
        System.out.println("@Value注解方式 获取的配置 zeh.test.age 为:" + age);
        int port = valueConfig.getPort();
        System.out.println("@Value注解方式 获取的配置 server.port 为:" + port);
        String tableName = valueConfig.getTableName();
        System.out.println("@Value注解方式 获取的配置 zeh.test.table-nAme 为:" + tableName);
        boolean myFlag = valueConfig.getMyFlag();
        System.out.println("@Value注解方式 获取的配置 zeh.test.flag 为:" + myFlag);
        String staticField = valueConfig.getStaticFieldStr();
        System.out.println("@Value注解方式 获取的配置 zeh.test.staticField 为:" + staticField);
        String constant = valueConfig.getConstantStr();
        System.out.println("@Value注解方式 获取的配置 zeh.test.constant 为:" + constant);

        List<String> yourList = valueConfig.getYourList();
        System.out.println("@Value注解方式 获取的配置 zeh.test.yourList 为:" + yourList);
        return "getConfigByValue";
    }
}

3.3 postman测试

访问连接:
http://localhost:8088/demo03GetConfigController/getConfigByValue
日志如下:

进入 getConfigByValue() 方法...
@Value注解方式 获取的配置 zeh.test.name 为:Eric Zhao , wo shi ni gege!
@Value注解方式 获取的配置 zeh.test.age 为:22
@Value注解方式 获取的配置 server.port 为:8088
@Value注解方式 获取的配置 zeh.test.table-nAme 为:Student
@Value注解方式 获取的配置 zeh.test.flag 为:false
@Value注解方式 获取的配置 zeh.test.staticField 为:null
@Value注解方式 获取的配置 zeh.test.constant 为:CONSTANT
@Value注解方式 获取的配置 zeh.test.yourList 为:[phone0.phone1, phone2]

4. 通过@ConfigurationProperties自定映射属性到配置类去获取配置项

配置类中各个属性字段都使用@Value去标注,很麻烦。
@ConfigurationProperties标注一个配置类后,只需要指定配置项的统一前缀,就可以将配置项自动映射到当前配置类的各个属性中。
因为是根据前缀就自动将配置项映射到配置类的字段中,所以这里面就存在一个映射规则,即它如何将配置项和属性进行匹配。
为了验证这个规则,我们着重关注配置文件中如下含有特殊符号的配置项:

zeh.test.table-nAme=Student
zeh.test.table-Name=Person

可以看到这两个配置项的key名称实际上是不同的,一个是“zeh.test.table-nAme”,另外一个是“zeh.test.table-Name”。
其中都有“-”,而且第一个的大小写很不规范。

4.1 编写配置类

code04controller.config.ConfigurationPropertiesConfig

package code04controller.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

// 读取配置方式3:自定义配置类用于读取springboot配置文件中的配置项。prefix指定配置项在配置文件中的前缀。
@ConfigurationProperties(prefix = "zeh.test")
@Component
// @ConfigurationProperties标注的配置类需要结合@Component使用,交给spring管理;当然也可以使用 @Component,@Service,@Controller,@Repository,@Bean 或者 @Configuration,一般都使用 @Component。
// 或者使用@EnableConfigurationProperties在需要注入该类的地方比如controller上进行标注;
// 或者在springboot启动类上标注@ConfigurationPropertiesScan指定需要扫描的package。
// 上面三种方式都可以将@ConfigurationProperties标注的配置类交给spring管理,从而可以使用@Autowire进行注入,否则注入的配置类对象将为null。
public class ConfigurationPropertiesConfig {
    // @ConfigurationProperties方式编写的配置类,必须有set方法,否则无法自动注入配置项。

    // 接收配置文件中的 name 属性,必须保持和配置文件中除了prefix前缀外剩下的属性名
    // spring默认做了一些处理:在映射时,会自动去掉配置项key中的“-”,并且在和配置类的属性字段名称匹配时,是不区分大小写的。
    // 简单点理解,它会首先去掉配置项中的“-”,然后将配置项的名称和配置类的字段名称统一转为小写进行匹配,第一个匹配到的配置项就会填充到对应的属性字段中。
    private String name;
    // 接收配置文件中的 age 属性,必须保持和配置文件中除了prefix前缀外剩下的属性名
    private Integer age;

    private int port;

    // 故意将这个字段的名称乱起,只要和配置项名称都转换成小写且去掉“-”后能匹配上就能完成自动注入。
    private String tableNaMe;

    // @ConfigurationProperties 无法为静态字段注入配置项
    private static String staticFieldStr;

    // @ConfigurationProperties 无法为常量字段注入配置项
    private final String constantStr = null;

    // @ConfigurationProperties可以注入嵌套map
    private Map<String, String> personMap;

    // @ConfigurationProperties 可以注入map
    private Map<String, String> studentMap;

    // @ConfigurationProperties 可以注入 list
    private List<String> myList;

    // 同上,@ConfigurationProperties 可以注入list
    private List<String> yourList;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getTableNaMe() {
        return tableNaMe;
    }

    public void setTableNaMe(String tableNaMe) {
        this.tableNaMe = tableNaMe;
    }

    public String getStaticFieldStr() {
        return staticFieldStr;
    }

    public void setStaticFieldStr(String staticFieldStr) {
        ConfigurationPropertiesConfig.staticFieldStr = staticFieldStr;
    }

    public String getConstantStr() {
        return constantStr;
    }

    public Map<String, String> getPersonMap() {
        return personMap;
    }

    public void setPersonMap(Map<String, String> personMap) {
        this.personMap = personMap;
    }

    public Map<String, String> getStudentMap() {
        return studentMap;
    }

    public void setStudentMap(Map<String, String> studentMap) {
        this.studentMap = studentMap;
    }

    public List<String> getMyList() {
        return myList;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public List<String> getYourList() {
        return yourList;
    }

    public void setYourList(List<String> yourList) {
        this.yourList = yourList;
    }
}

ConfigurationProperties注解的源码可以参考如下:

package org.springframework.boot.context.properties;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Indexed;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
    @AliasFor("prefix")
    String value() default "";

    @AliasFor("value")
    String prefix() default "";

    boolean ignoreInvalidFields() default false;

    boolean ignoreUnknownFields() default true;
}

注意:
(1)@ConfigurationProperties标注的配置类必须交给spring管理,有如下三种方式:
a.在当前配置类上使用@Compeonent或者@Service等方式标注;比如使用 @Component,@Service,@Controller,@Repository,@Bean,或者 @Configuration,当然一般都使用 @Component。
b.在需要注入该配置类的类上面使用@EnableConfigurationProperties注解标注,指定需要开启的该配置类的class对象,比如在Demo03GetConfigController上标注:@EnableConfigurationProperties(ConfigurationPropertiesConfig.class);
c.在springboot启动类上标注@ConfigurationPropertiesScan注解,指定需要批量扫描的ConfigurationProperties配置类所在包路径,可以完成批量扫描后交给spring容器管理。
(2)该配置类必须标注@ConfigurationProperties(prefix = “zeh.test”),其中prefix属性指定要读取的配置项的统一前缀。
(3)使用@ConfigurationProperties标注的配置类必须有set方法,否则无法自动注入配置项。
(4)自动注入配置项的匹配规则是:
配置类的属性字段命名必须和配置文件中去掉前缀后的剩余命名保持一致,spring在比较时,会自动去除配置项名称中的”-“,然后将配置项的剩余名称和配置类的属性名统一转换为小写进行匹配,第一个匹配上的配置项将注入给目标属性,后续匹配到的不再进行注入。
(5)@ConfigurationProperties标注的实体类无法为静态字段注入配置项;
(6)@ConfigurationProperties标注的实体类无法为常量字段注入配置项;
(7)@ConfigurationProperties标注的实体类可以注入嵌套map和嵌套list。

4.2 在上面的controller中增加方法

code04controller.controller.Demo03GetConfigController

package code04controller.controller;

import code04controller.config.ConfigurationPropertiesConfig;
import code04controller.config.EnvironmentConfig;
import code04controller.config.ValueConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

// 测试springboot读取文件的各种方式
@RestController
@RequestMapping("demo03GetConfigController")
public class Demo03GetConfigController {

    // 向当前controller中注册spring管理的ConfigurationPropertiesConfig
    @Autowired
    private ConfigurationPropertiesConfig configurationPropertiesConfig;

    // 测试通过@Value注解获取springboot配置项
    @GetMapping("getConfigByConfigurationProperties")
    public String getConfigByConfigurationProperties() {
        System.out.println("进入 getConfigByConfigurationProperties() 方法...");
        String name = configurationPropertiesConfig.getName();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.name 为:" + name);
        int age = configurationPropertiesConfig.getAge();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.age 为:" + age);
        int port = configurationPropertiesConfig.getPort();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 server.port 为:" + port);
        String tableName = configurationPropertiesConfig.getTableNaMe();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.table-nAme 为:" + tableName);
        String staticFieldStr = configurationPropertiesConfig.getStaticFieldStr();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.staticField 为:" + staticFieldStr);
        String constantStr = configurationPropertiesConfig.getConstantStr();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.constant 为:" + constantStr);
        Map<String,String> personMap = configurationPropertiesConfig.getPersonMap();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.personMap 为:" + personMap);
        Map<String,String> studentMap = configurationPropertiesConfig.getStudentMap();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.studentMap 为:" + studentMap);
        List<String> myList = configurationPropertiesConfig.getMyList();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.myList 为:" + myList);
        List<String> yourList = configurationPropertiesConfig.getYourList();
        System.out.println("@ConfigurationProperties注解方式 获取的配置 zeh.test.yourList 为:" + yourList);
        return "getConfigByConfigurationProperties";
    }
}

4.3 postman进行测试

访问连接:
http://localhost:8088/demo03GetConfigController/getConfigByConfigurationProperties
日志如下:

进入 getConfigByConfigurationProperties() 方法...
@ConfigurationProperties注解方式 获取的配置 zeh.test.name 为:Eric Zhao
@ConfigurationProperties注解方式 获取的配置 zeh.test.age 为:22
@ConfigurationProperties注解方式 获取的配置 server.port 为:0
@ConfigurationProperties注解方式 获取的配置 zeh.test.table-nAme 为:Student
@ConfigurationProperties注解方式 获取的配置 zeh.test.staticField 为:null
@ConfigurationProperties注解方式 获取的配置 zeh.test.constant 为:null
@ConfigurationProperties注解方式 获取的配置 zeh.test.personMap 为:{personName=zhangsan, personHobby=tiaowu}
@ConfigurationProperties注解方式 获取的配置 zeh.test.studentMap 为:{studentSex=man, studentUrl=www.zeh.com}
@ConfigurationProperties注解方式 获取的配置 zeh.test.myList 为:[apple0, apple1, apple2]
@ConfigurationProperties注解方式 获取的配置 zeh.test.yourList 为:[phone0.phone1, phone2]

可以看到,前缀为“zeh.test”的,且剩余配置项名称去除“-”转小写后和配置类属性名称转小写后能够匹配上的,都已经自动将配置项的值注入进来的。
“server.port”的前缀不是“zeh.test”,所以它的值没有注入进来。
配置项“zeh.test.table-Name=Person”,尽管按照规则去除“-”后转小写也和属性“tableNaMe”转小写后能够匹配到,但以为它在后面读取,而配置项“zeh.test.table-nAme=Student”已经匹配到了,因此它被抛弃了。

扩展1:@EnableConfigurationProperties 方式将@ConfigurationProperties标注的类交给spring管理。
去除ConfigurationPropertiesConfig类上面标注的@Component注解:

package code04controller.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

// 去除当前配置类上的@Component注解。
@ConfigurationProperties(prefix = "zeh.test")
public class ConfigurationPropertiesConfig {
    // 为了节省篇幅无关字段删除无关字段
}

Demo03GetConfigController上新增@EnableConfigurationProperties(ConfigurationPropertiesConfig.class)注解,表示当前controller需要开启ConfigurationProperties配置类的注入:

package code04controller.controller;

@RestController
@RequestMapping("demo03GetConfigController")
@EnableConfigurationProperties(ConfigurationPropertiesConfig.class) // 标注该注解,则通过 @Autowired 也可以注入 ConfigurationPropertiesConfig 对象。   
public class Demo03GetConfigController {

    // 向当前controller中注册spring管理的ConfigurationPropertiesConfig
    @Autowired
    private ConfigurationPropertiesConfig configurationPropertiesConfig;

}

org.springframework.boot.context.properties.EnableConfigurationPropertie源码如下:

package org.springframework.boot.context.properties;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EnableConfigurationPropertiesRegistrar.class})
public @interface EnableConfigurationProperties {
    String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";

    Class<?>[] value() default {};
}

可以看到,@EnableConfigurationProperties 注解接收一个class类型的数组,这意味着我们可以通知启动多个被@ConfigurationProperties标注的class,将其全部交给spring管理。

扩展2:@ConfigurationPropertiesScan 方式将@ConfigurationProperties标注的类批量交给spring管理。
同样,去除ConfigurationPropertiesConfig类上面标注的@Component注解:

package code04controller.config;
   
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

// 去除当前配置类上的@Component注解。
@ConfigurationProperties(prefix = "zeh.test")
public class ConfigurationPropertiesConfig {
   // 为了节省篇幅无关字段删除无关字段
}

code04controller.controller.Demo03GetConfigController如下:

package code04controller.controller;

// 测试springboot读取文件的各种方式
@RestController
@RequestMapping("demo03GetConfigController")
public class Demo03GetConfigController {

    // 向当前controller中注册spring管理的ConfigurationPropertiesConfig
    @Autowired
    private ConfigurationPropertiesConfig configurationPropertiesConfig;

}

springboot启动类上使用@ConfigurationPropertiesScan标注:

package code04controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.scheduling.annotation.EnableAsync;

//默认扫描当前类所在包和当前子包的所有注解代码
@SpringBootApplication
@EnableAsync
//开启异步执行任务
@ConfigurationPropertiesScan("code04controller.config") // 批量扫描code04controller.config包下被@ConfigurationProperties标注的配置类交给spring管理。
public class Code04ControllerApplication {
    public static void main(String[] args) {
        SpringApplication.run(Code04ControllerApplication.class, args);
    }
}

org.springframework.boot.context.properties.ConfigurationPropertiesScan源码如下:

package org.springframework.boot.context.properties;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ConfigurationPropertiesScanRegistrar.class})
@EnableConfigurationProperties
public @interface ConfigurationPropertiesScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

可以看到,如果多个不同的包下都有@ConfigurationProperties标注的类,则可以同时指定多个包路径,因为它接收的是一个字符串数组。

5. @PropertySource注解指定外部配置文件的读取

上面的方式都只能读取springboot的默认配置文件,比如application.properties或者对应的profile文件,如果我们想自定义一个配置文件,那么springboot该如何读取该配置文件中的配置项呢?
其实很简单,读取的方式和上面的@Value或者@ConfigurationProperties标注的方式一样,我们只需要在对应的配置类上标注@PropertySource注解,指定我们要读取的外部配置文件的路径即可。
org.springframework.context.annotation.PropertySource的源码如下:

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.io.support.PropertySourceFactory;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
    String name() default "";

    String[] value();

    boolean ignoreResourceNotFound() default false;

    String encoding() default "";

    Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

ignoreResourceNotFound = true表示当自定义的配置文件不存在时,启动不报错;默认是false,当指定的配置文件不存在时将启动报错。
value是一个数组,用于指定多个配置文件的相对路径,比如:

@PropertySource(value = {"classpath:jdbc.properties"}, ignoreResourceNotFound = true)

指定多个配置文件:

@PropertySource(value = {"classpath:jdbc.properties","classpath:test.properties","classpath:user.properties"}, ignoreResourceNotFound = true)

5.1 resource下自定义配置文件

user.properties

# 自定义一个配置文件,使用@PropertySource指定该文件   

# 下面两个配置项是这个自定义文件中单独搞的   

eric.englishName=Zhaoerhu
eric.englishAge=22

# 下面两个配置项和application.properties中的配置项定义的一样,使用@ConfigurationProperties指定前缀去映射   

zeh.test.username=Yurui
zeh.test.userAge=18

# 下面两个配置项和application.properties中的配置项定义的一样,使用@Value去单独读取   

zeh.test.name=ZHOU
zeh.test.flag=true

5.2 编写一个配置类

code04controller.config.PropertySourceConfig

package code04controller.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

// 读取配置方式4:使用@PropertySource注解读取自定义配置类,需要结合@Value注解或者@ConfigurationProperties注解一起使用。
@PropertySource(value = {"classpath:user.properties"}, ignoreResourceNotFound = true)
@ConfigurationProperties(prefix = "zeh.test")
@Component
public class PropertySourceConfig {

    // application.properties和user.properties中都有完全相同的配置,@ConfigurationProperties自动映射注入
    private String userName;

    // application.properties和user.properties中都有完全相同的配置,@ConfigurationProperties自动映射注入
    private int userAge;

    // zeh.test.flag配置项,application.properties和user.properties中都有完全相同的配置,@ConfigurationProperties自动映射注入
    private boolean flag;

    // user.properties自定义配置文件中的独立配置项,使用@Value注入
    @Value("${eric.englishName}")
    private String englishName;

    // user.properties自定义配置文件中的独立配置项,使用@Value注入
    @Value("${eric.englishAge}")
    private int englishAge;

    // application.properties和user.properties中都有完全相同的配置,使用@Value注入
    @Value("${zeh.test.name}")
    private String testName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public int getUserAge() {
        return userAge;
    }

    public void setUserAge(int userAge) {
        this.userAge = userAge;
    }

    public String getEnglishName() {
        return englishName;
    }

    public int getEnglishAge() {
        return englishAge;
    }

    public String getTestName() {
        return testName;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

注意,上面的配置类,既同时使用@ConfigurationProperties来批量读取相同前缀的配置项来完成属性的自动映射,也同时使用了@Value来获取配置文件中的指定目标配置项。
这两者可以同时使用,所读取的目标配置文件由@PropertySource注解指定,即自定义的配置user.properties。
尽管使用@PropertySource指定了目标配置文件,但是springboot默认还是优先从application.properties等默认配置中查找配置项,一旦找到,则不再从此处指定的目标配置中读取配置项;
只有当application.properties中不存在目标配置项时,才从@PropertySource指定的配置文件中读取。

5.3 编写controller

package code04controller.controller;

import code04controller.config.ConfigurationPropertiesConfig;
import code04controller.config.EnvironmentConfig;
import code04controller.config.PropertySourceConfig;
import code04controller.config.ValueConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

// 测试springboot读取文件的各种方式
@RestController
@RequestMapping("demo03GetConfigController")
public class Demo03GetConfigController {

    // 向当前controller中注入PropertySourceConfig
    @Autowired
    private PropertySourceConfig propertySourceConfig;

     // 测试@PropertySource注解获取springboot配置项
     // @PropertySource尽管可以指定读取自定义配置文件,但是springboot还是优先从默认配置文件中找寻配置项,一旦找到,将不再读取自定义配置文件中的配置项
     @GetMapping("getConfigByPropertySource")
     public String getConfigByPropertySource() {
         System.out.println("进入 getConfigByPropertySource() 方法...");
         String englishName = propertySourceConfig.getEnglishName();
         System.out.println("@PropertySource注解方式 获取的配置 eric.englishName 为:" + englishName);
         int englishAge = propertySourceConfig.getEnglishAge();
         System.out.println("@PropertySource注解方式 获取的配置 eric.englishAge 为:" + englishAge);
         String userName = propertySourceConfig.getUserName();
         System.out.println("@PropertySource注解方式 获取的配置 zeh.test.username 为:" + userName);
         int userAge = propertySourceConfig.getUserAge();
         System.out.println("@PropertySource注解方式 获取的配置 zeh.test.userAge 为:" + userAge);
         String testName = propertySourceConfig.getTestName();
         System.out.println("@PropertySource注解方式 获取的配置 zeh.test.name 为:" + testName);
         boolean flag = propertySourceConfig.isFlag();
         System.out.println("@PropertySource注解方式 获取的配置 zeh.test.flag 为:" + flag);
         return "getConfigByPropertySource";
     }
}

5.4 postman测试

访问连接:
http://localhost:8088/demo03GetConfigController/getConfigByPropertySource
日志:

进入 getConfigByPropertySource() 方法...
@PropertySource注解方式 获取的配置 eric.englishName 为:Zhaoerhu
@PropertySource注解方式 获取的配置 eric.englishAge 为:22
@PropertySource注解方式 获取的配置 zeh.test.username 为:Yurui
@PropertySource注解方式 获取的配置 zeh.test.userAge 为:18
@PropertySource注解方式 获取的配置 zeh.test.name 为:Eric Zhao
@PropertySource注解方式 获取的配置 zeh.test.flag 为:false

从日志可看出,当@PropertySource指定的目标配置文件中配置了和springboot默认配置文件中名称完全相同的配置项时, 不论是@ConfigurationProperties批量自动映射的方式,还是@Value显式映射的方式,springboot都默认优先从默认配置中读取配置项,一旦读取到,将不再读取@PropertySource指定的目标配置文件;只有当默认配置文件中读取不到目标配置项时,才从@PropertySource指定的目标配置文件中读取!!!

6. 扩展-使用@Bean来完成配置项类的实例化

上面不论是使用@Value还是@ConfigurationProperties的方式去加载配置项,都使用了@Component的方式直接标注在配置项类上面。
除此之外,我们还可以使用@Bean的方式将配置项类交给spring去管理。
使用@Bean的方式将某个类交给spring去实例化,大多数情况下都是因为这个类不是我们源码中的,是在其他jar包中存在的类,我们没法直接在其上面修改,即我们没法在源码上使用@Component等注解去标注,所以才会采用@Bean的方式将其交给spring去管理!!!

下面我们看一个数据源配置的案例,以为数据源的类DataSource ,是spring jar包中定义好的,我们没法修改,因为,我们想将这个类交给spring管理,最简单的方式就是使用@Bean去标注在其方法上。

@Configuration
public class DataSourceConfig {

	@Primary
	@Bean(name = "primaryDataSource")
	@ConfigurationProperties(prefix="spring.datasource.primary")
	public DataSource primaryDataSource() {
		return DataSourceBuilder.create().build();
	}

}

这里便是将前缀为“spring.datasource.primary”的属性,赋值给DataSource对应的属性值。
@Configuration注解的配置类中通过@Bean注解在某个方法上将方法返回的对象定义为一个Bean,并使用配置文件中相应的属性初始化该Bean的属性。

文档信息

Search

    Table of Contents