Spring Boot中的配置文件使用以及重新加载

Spring Boot中的配置文件使用以及重新加载

概要

本教程将展示如何通过Java configuration和@PropertySource或XML和property-placeholder在Spring中设置和使用属性

通过Java Annotations注册配置文件

Spring 3.1起引入了新的@PropertySource注释,作为向环境中添加属性源的方便机制。
该注释将与基于java的配置和@Configuration注释一起使用:

@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {
    //...
}

另一种非常有用的注册新属性文件的方法是使用占位符,它允许在运行时动态选择正确的文件:

@PropertySource({ 
  "classpath:persistence-${envTarget:mysql}.properties"
})

${envTarget:mysql} , envTarget:环境变量名称 , mysql:默认值

定义多个属性文件

在Java 8中@PropertySource注释是可重复的。因此,我们可以使用这个注释来定义多个属性位置:


@PropertySource("classpath:foo.properties")
@PropertySource("classpath:bar.properties")
public class PropertiesWithJavaConfig {
    //...
}

当然也可以使用数组的形式:

@PropertySources({
    @PropertySource("classpath:foo.properties"),
    @PropertySource("classpath:bar.properties")
})
public class PropertiesWithJavaConfig {
    //...
}

注入属性值

  • 用@Value注释注入属性很简单
@Value( "${jdbc.url}" )
private String jdbcUrl;

  • 在注入的时候使用默认值
@Value( "${jdbc.url:aDefaultUrl}" )
private String jdbcUrl;

  • 使用Environment API获取属性值
@Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));

@Value使用

@Value注解可用于将值注入spring管理bean中的字段,并且可以应用于字段或构造函数/方法参数级别。

  • 注入硬编码值到字段中(没有任何意义)
@Value("string value")
private String stringValue;
  • 使用@PropertySource注释允许我们使用@Value注释处理来自属性文件的值。在下面的例子中,我们从分配给字段的文件中获取值:
@Value("${value.from.file}")
private String valueFromFile;

  • 我们也可以用相同的语法从系统属性中设置值,假设我们已经定义了一个系统属性systemValue:
@Value("${systemValue}")
private String systemValue;
  • 可以为未定义的属性提供默认值。一些默认值将被注入:
@Value("${unknown.param:some default}")
private String someDefault;

如果将相同的属性定义为系统属性以及属性文件中,则将优先应用系统属性,系统属性 > 配置文件属性

  • 数组属性设置
listOfValues = A,B,C
@Value("${listOfValues}")
private String[] valuesArray;

  • map 属性设置

我们还可以使用@Value注释来注入一个Map属性。首先,我们需要在属性文件的{key: ’ value’}中定义属性:

valuesMap={key1: '1', key2: '2', key3: '3'}
@Value("#{${valuesMap}}")
private Map valuesMap;
  • 如果我们需要在映射中获取特定键的值,我们所要做的就是在表达式中添加键的名称:
@Value("#{${valuesMap}.key1}")
private Integer valuesMapKey1;
  • 安全的使用表达式
@Value("#{${valuesMap}['unknownKey']}")
private Integer unknownMapKey;
  • 为map设定默认值
@Value("#{${unknownMap : {key1: '1', key2: '2'}}}")
private Map unknownMap;
 
@Value("#{${valuesMap}['unknownKey'] ?: 5}")
private Integer unknownMapKeyWithDefaultValue;

  • 使用@Value注入所有的系统变量
@Value("#{systemProperties}")
private Map systemPropertiesMap;

  • 使用SpEL设置属性
@Component
@PropertySource("classpath:values.properties")
public class CollectionProvider {
 
    private List values = new ArrayList<>();
 
    @Autowired
    public void setValues(@Value("#{'${listOfValues}'.split(',')}") List values) {
        this.values.addAll(values);
    }
 
    // standard getter
}

Spring Boot 中配置属性

在Spring Boot工程目录src/main/resources中的application.properties会被自动加载,无需额外配置就可以使用

  • 1、在运行的时候指定配置文件
java -jar app.jar --spring.config.location=classpath:/another-location.properties

  • 2、在Spring Boot2.3后可以指定配置文件通配符
java -jar app.jar --spring.config.location=config/*/

  • 3、通过环境变量设置属性文件

如果我们需要针对不同的环境设置属性文件。可以简单地定义应用程序环境属性文件,然后设置一个具有相同环境名称的Spring配置文件。
例如,如果我们定义了一个“stage”环境,这意味着我们必须定义一个stage配置文件application-stage.properties。
此env文件将被加载,并优先于默认的属性文件。这时候,默认文件仍将被加载,只是当存在属性冲突时,使用环境的属性覆盖默认值。

  • 4、测试的时候使用属性文件

有时在测试应用程序时,我们可能还需要使用不同的属性值。
Spring引导通过在测试运行期间查看src/test/resources目录来为我们处理这个问题。同样,默认属性仍然可以正常注入,但是如果发生冲突,默认属性将被覆盖。

  • 5、单元测试中使用属性文件

如果我们需要对测试属性进行更细粒度的控制,那么我们可以使用@TestPropertySource注释。
这允许我们为特定的测试上下文设置测试属性,优先于默认的属性源:

@RunWith(SpringRunner.class)
@TestPropertySource("/foo.properties")
public class FilePropertyInjectionUnitTest {
 
    @Value("${foo}")
    private String foo;
 
    @Test
    public void whenFilePropertyProvided_thenProperlyInjected() {
        assertThat(foo).isEqualTo("bar");
    }
}

  • 6、如果不需要属性文件也可以直接注入配置项
@RunWith(SpringRunner.class)
@TestPropertySource(properties = {"foo=bar"})
public class PropertyInjectionUnitTest {
 
    @Value("${foo}")
    private String foo;
 
    @Test
    public void whenPropertyProvided_thenProperlyInjected() {
        assertThat(foo).isEqualTo("bar");
    }
}

把属性注入到对象中

我们可以使用@ConfigurationProperties注释,它将这些属性层次映射到Java对象中

  • 数据库链接属性
database.url=jdbc:postgresql:/localhost:5432/instance
database.username=foo
database.password=bar

或者YAML文件

database:
  url: jdbc:postgresql:/localhost:5432/instance
  username: foo
  password: bar
secret: foo
  • 使用@ConfigurationProperties 注入属性
@ConfigurationProperties(prefix = "database")
public class Database {
    String url;
    String username;
    String password;
 
    // standard getters and setters
}

Spring Boot应用它的约定优于配置的规则,自动在属性名和它们对应的字段之间进行映射,我们需要提供的只是属性前缀

通过命令行设置属性

除了使用属性文件外,我们也可以在启动程序的时候使用命令行传入参数

java -jar app.jar --property="value"

传入环境变量参数

java -Dproperty.name="value" -jar app.jar

Spring Boot 会自动探测环境变量

export name=value
java -jar app.jar

@ConfigurationProperties 使用详解

简单属性映射

@Configuration
@ConfigurationProperties(prefix = "mail")
public class ConfigProperties {
    
    private String hostName;
    private int port;
    private String from;
 
    // standard getters and setters
}
  • 我们使用@Configuration,这样Spring就可以在应用程序上下文中创建一个Spring bean。

  • @ConfigurationProperties最适合使用具有相同前缀的层次属性;因此,我们添加一个mail前缀。Spring框架使用标准的Java bean setter,因此我们必须为每个属性声明setter方法。

  • 注意:如果我们在POJO中不使用**@Configuration**,那么我们需要在主Spring应用程序类中添加**@EnableConfigurationProperties(ConfigProperties.class)**来将属性绑定到POJO中:

@SpringBootApplication
@EnableConfigurationProperties(ConfigProperties.class)
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Spring将自动绑定属性文件中定义的任何属性,这些属性具有前缀mail,并且与ConfigProperties类中的一个字段同名

Spring Boot 自动/定制绑定属性

Spring Boot通过类路径扫描查找和注册@ConfigurationProperties类。因此,不需要用@Component(和其他元注释,如@Configuration)注释这样的类,甚至也不需要使用@EnableConfigurationProperties

@ConfigurationProperties(prefix = "mail") 
public class ConfigProperties { 
 
    private String hostName; 
    private int port; 
    private String from; 
 
    // standard getters and setters 
}

由@SpringBootApplication启用的类路径扫描器会找到ConfigProperties类,不需要做额外配置。

此外,我们可以使用@ConfigurationPropertiesScan注释来扫描配置属性类的自定义位置:

@SpringBootApplication
@ConfigurationPropertiesScan("com.baeldung.properties")
public class DemoApplication { 
 
    public static void main(String[] args) {   
        SpringApplication.run(DemoApplication.class, args); 
    } 
}

上述例子中Spring Boot将只在com.baeldung中查找配置属性类

嵌套属性绑定

我们可以在列表、映射和类中使用嵌套属性

属性类

public class ConfigProperties {
 
    private String host;
    private int port;
    private String from;
    private List defaultRecipients;
    private Map additionalHeaders;
    private Credentials credentials;
 
    // standard getters and setters
}

属性文件示例

# 简单属性
[email protected]
mail.port=9000
[email protected]
 
# List 属性
mail.defaultRecipients[0][email protected]
mail.defaultRecipients[1][email protected]
 
# Map 属性
mail.additionalHeaders.redelivery=true
mail.additionalHeaders.secure=true
 
# Object 对象属性
mail.credentials.username=john
mail.credentials.password=password
mail.credentials.authMethod=SHA1

@Bean 注释的方法上使用 @ConfigurationProperties

我们还可以在带有@ bean注释的方法上使用@ConfigurationProperties注释。
当我们想要将属性绑定到我们无法控制的第三方组件时,这种方法可能特别有用。

让我们创建一个简单的Item类,我们将在下一个例子中使用:

  • 属性类
public class Item {
    private String name;
    private int size;
 
    // standard getters and setters
}

现在让我们看看如何在@Bean方法上使用@ConfigurationProperties来将外部化的属性绑定到项目实例:

  • 属性文件
# 简单属性
[email protected]
item.size=32
  • 通过@Configuration 构造类
@Configuration
public class ConfigProperties {
 
    @Bean
    @ConfigurationProperties(prefix = "item")
    public Item item() {
        return new Item();
    }
}

因此,任何带有itme前缀的属性都将映射到Spring上下文管理的item实例中

属性验证

@ConfigurationProperties使用JSR-303格式提供对属性的验证

  • 例如,让我们强制设置hostName属性:
@NotBlank
private String hostName;
  • 将authMethod属性设置为1到4个字符长:
@Length(max = 4, min = 1)
private String authMethod;

  • 端口属性从1025到65536:
@Min(1025)
@Max(65536)
private int port;
  • 从属性必须匹配的电子邮件地址格式:
@Pattern(regexp = "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,6}$")
private String from;

这帮助我们减少了代码中的很多if-else条件判断,使代码看起来更干净、更简洁。

如果其中任何一个验证失败,则主应用程序将无法启动并抛出IllegalStateException异常。

Hibernate验证框架使用标准的Java bean getter和setter,因此为每个属性声明getter和setter非常重要。

属性自定义转换

我们还可以添加自己的自定义转换器来支持将属性转换为特定的类类型。

让我们添加一个简单的类Employee:

public class Employee {
    private String name;
    private double salary;
}

然后我们将创建一个自定义转换器来转换这个属性:

conversion.employee=john,2000

我们需要实现转换器接口,然后使用@ConfigurationPropertiesBinding注释来注册我们的自定义转换器:

@Component
@ConfigurationPropertiesBinding
public class EmployeeConverter implements Converter {
 
    @Override
    public Employee convert(String from) {
        String[] data = from.split(",");
        return new Employee(data[0], Double.parseDouble(data[1]));
    }
}

属性构造器绑定,不可变属性

从Spring Boot 2.2开始,我们可以使用@ConstructorBinding注释来绑定配置属性。
这本质上意味着@configurationproperty注释的类现在可能是不可变的。

@ConfigurationProperties(prefix = "mail.credentials")
@ConstructorBinding
public class ImmutableCredentials {
 
    private final String authMethod;
    private final String username;
    private final String password;
 
    public ImmutableCredentials(String authMethod, String username, String password) {
        this.authMethod = authMethod;
        this.username = username;
        this.password = password;
    }
 
    public String getAuthMethod() {
        return authMethod;
    }
 
    public String getUsername() {
        return username;
    }
 
    public String getPassword() {
        return password;
    }
}
  • 上述例子中,在使用@ConstructorBinding时,我们需要向构造函数提供我们想要绑定的所有参数。

  • 所有不变的向量的域都是最终的。而且,没有setter方法。

  • 此外,需要强调的是,要使用构造函数绑定,我们需要显式地使用@EnableConfigurationProperties或@ConfigurationPropertiesScan启用配置类

Spring Boot 重新加载属性文件

在Spring中我们有不同的访问属性的选项:

  • 1、Environment– 我们可以注入Environment,然后使用Environment#getProperty读取给定的属性。环境包含不同的属性源,如系统属性、-D参数和application.properties(.yml)。另外,可以使用@PropertySource向环境中添加额外的属性源。
  • 2、Properties– 我们可以将属性文件加载到Properties实例中,然后通过调用Properties#get(“property”)在bean中使用它。
  • 3、@Value– 我们可以通过@Value(${’ property’})注释在bean中注入一个特定的属性
  • 4、@ConfigurationProperties– 我们可以使用@ConfigurationProperties加载bean中的分层属性

从外部文件中重载配置文件

要在运行时更改文件中的属性,我们应该将该文件放在jar之外的某个地方。然后,我们将通过命令行参数-spring.config告诉Spring从哪里获取配置文件。spring.config.location=file://{path to file}。或者,我们可以把它放到application.properties中。

在基于文件的属性中,我们必须选择一种方法来重新加载文件。例如,我们可以开发一个端点或调度程序来读取文件和更新属性。

重新加载文件的一个类库是Apache的commons-configuration,我们可以使用不同重载策略PropertiesConfiguration。

增加 commons-configuration 依赖到 pom.xml文件中:


    commons-configuration
    commons-configuration
    1.10


然后,我们添加一个方法来创建PropertiesConfiguration bean,稍后我们将使用它:

@Bean
@ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false)
public PropertiesConfiguration propertiesConfiguration(
  @Value("${spring.config.location}") String path) throws Exception {
    String filePath = new File(path.substring("file:".length())).getCanonicalPath();
    PropertiesConfiguration configuration = new PropertiesConfiguration(
      new File(filePath));
    configuration.setReloadingStrategy(new FileChangedReloadingStrategy());
    return configuration;
}

检查配置文件中是否spring.config.location属性
@Value("${spring.config.location}" 注入配置文件地址
我们将FileChangedReloadingStrategy设置为具有默认刷新延迟的重新加载策略。这意味着PropertiesConfiguration检查文件修改日期,每5000ms检查一次。

从Environment环境变量中重载属性

如果我们想要重新加载通过环境实例加载的属性,我们必须扩展PropertySource,然后使用PropertiesConfiguration从外部属性文件返回新值。

让我们从扩展PropertySource开始:

public class ReloadablePropertySource extends PropertySource {
 
    PropertiesConfiguration propertiesConfiguration;
 
    public ReloadablePropertySource(String name, PropertiesConfiguration propertiesConfiguration) {
        super(name);
        this.propertiesConfiguration = propertiesConfiguration;
    }
 
    public ReloadablePropertySource(String name, String path) {
        super(StringUtils.isEmpty(name) ? path : name);
        try {
            this.propertiesConfiguration = new PropertiesConfiguration(path);
            this.propertiesConfiguration.setReloadingStrategy(new FileChangedReloadingStrategy());
        } catch (Exception e) {
            throw new PropertiesException(e);
        }
    }
 
    @Override
    public Object getProperty(String s) {
        return propertiesConfiguration.getProperty(s);
    }
}

覆盖了PropertySource#getProperty方法,将其委托给PropertiesConfiguration#getProperty。因此,它会根据刷新延迟间隔检查更新的值。

现在,我们将把ReloadablePropertySource添加到环境的属性源:

@Configuration
public class ReloadablePropertySourceConfig {
 
    private ConfigurableEnvironment env;
 
    public ReloadablePropertySourceConfig(@Autowired ConfigurableEnvironment env) {
        this.env = env;
    }
 
    @Bean
    @ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false)
    public ReloadablePropertySource reloadablePropertySource(PropertiesConfiguration properties) {
        ReloadablePropertySource ret = new ReloadablePropertySource("dynamic", properties);
        MutablePropertySources sources = env.getPropertySources();
        sources.addFirst(ret);
        return ret;
    }
}

上面添加了新的属性源作为第一项,因为我们希望它用相同的键覆盖任何现有的属性。

让我们创建一个bean来从环境中读取属性:

@Component
public class EnvironmentConfigBean {
 
    private Environment environment;
 
    public EnvironmentConfigBean(@Autowired Environment environment) {
        this.environment = environment;
    }
 
    public String getColor() {
        return environment.getProperty("application.theme.color");
    }
}

如果我们需要添加其他可重新加载的外部属性源,首先我们必须实现自定义的PropertySourceFactory:

public class ReloadablePropertySourceFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource createPropertySource(String s, EncodedResource encodedResource)
      throws IOException {
        Resource internal = encodedResource.getResource();
        if (internal instanceof FileSystemResource)
            return new ReloadablePropertySource(s, ((FileSystemResource) internal)
              .getPath());
        if (internal instanceof FileUrlResource)
            return new ReloadablePropertySource(s, ((FileUrlResource) internal)
              .getURL()
              .getPath());
        return super.createPropertySource(s, encodedResource);
    }
}

然后我们可以用@PropertySource注释组件的类:

@PropertySource(value = "file:path-to-config", factory = ReloadablePropertySourceFactory.class)

@ConfigurationProperties @Value 重新加载属性的限制

要使用@ConfigurationProperties获得同样的效果,我们需要重新构造实例。
但是,Spring将只创建具有原型或请求范围的组件的新实例。

因此,我们重新加载环境的技术也适用于上述情况,但是对于单例,我们别无选择,只能实现一个端点来销毁和重新创建bean,或者在bean本身内部处理属性的重新加载。

使用Actuator 和 Cloud重新加载配置

Spring Actuator 为服务运行状况、指标和配置提供了不同的接口,但没有为刷新bean提供任何接口。因此,我们需要Spring Cloud向其添加/refresh接口。此接口将重新加载Environment的所有属性源,然后发布一个EnvironmentChangeEvent。

Spring Cloud还引入了@RefreshScope,我们可以将其用于配置类或bean。因此,默认范围将是refresh而不是singleton。
使用refresh作用域,Spring将在一个EnvironmentChangeEvent上清除这些组件的内部缓存。然后,在下一次访问bean时,创建一个新的实例。


    org.springframework.boot
    spring-boot-starter-actuator


引入Spring Cloud依赖


    
        
            org.springframework.cloud
            spring-cloud-dependencies
            ${spring-cloud.version}
            pom
            import
        
    

 

    Greenwich.SR1



    org.springframework.cloud
    spring-cloud-starter


最后,让我们启用refresh端点:

management.endpoints.web.exposure.include=refresh

当我们使用Spring Cloud时,我们可以设置配置服务器来管理属性,但是我们也可以继续使用外部文件。下面,我们可以演示两种读取属性的方法:@Value和@ConfigurationProperties。

@ConfigurationProperties 刷新

@Component
@ConfigurationProperties(prefix = "application.theme")
@RefreshScope
public class ConfigurationPropertiesRefreshConfigBean {
    private String color;
 
    public void setColor(String color) {
        this.color = color;
    }
 
    //getter and other stuffs
}

ConfigurationPropertiesRefreshConfigBean,从application.theme读取“color”属性。注意,根据Spring 约定属性color需要setColor方法

在更改了application.theme的值之后。在我们的外部配置文件中,我们可以调用/refresh,这样,我们就可以在下一次访问时从bean中获得新的值。

@Value 刷新

@Component
@RefreshScope
public class ValueRefreshConfigBean {
    private String color;
 
    public ValueRefreshConfigBean(@Value("${application.theme.color}") String color) {
        this.color = color;
    } 
    //put getter here 
}

但是,有必要注意,/refresh不适用于具有显式单例作用域的bean

你可能感兴趣的:(spring,boot,教程,spring,spring,boot,java)