本教程将展示如何通过Java configuration和@PropertySource或XML和property-placeholder在Spring中设置和使用属性
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( "${jdbc.url}" )
private String jdbcUrl;
@Value( "${jdbc.url:aDefaultUrl}" )
private String jdbcUrl;
@Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));
@Value注解可用于将值注入spring管理bean中的字段,并且可以应用于字段或构造函数/方法参数级别。
@Value("string value")
private String stringValue;
@Value("${value.from.file}")
private String valueFromFile;
@Value("${systemValue}")
private String systemValue;
@Value("${unknown.param:some default}")
private String someDefault;
如果将相同的属性定义为系统属性以及属性文件中,则将优先应用系统属性,系统属性 > 配置文件属性
listOfValues = A,B,C
@Value("${listOfValues}")
private String[] valuesArray;
我们还可以使用@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;
@Value("#{${unknownMap : {key1: '1', key2: '2'}}}")
private Map unknownMap;
@Value("#{${valuesMap}['unknownKey'] ?: 5}")
private Integer unknownMapKeyWithDefaultValue;
@Value("#{systemProperties}")
private Map systemPropertiesMap;
@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工程目录src/main/resources中的application.properties会被自动加载,无需额外配置就可以使用
java -jar app.jar --spring.config.location=classpath:/another-location.properties
java -jar app.jar --spring.config.location=config/*/
如果我们需要针对不同的环境设置属性文件。可以简单地定义应用程序环境属性文件,然后设置一个具有相同环境名称的Spring配置文件。
例如,如果我们定义了一个“stage”环境,这意味着我们必须定义一个stage配置文件application-stage.properties。
此env文件将被加载,并优先于默认的属性文件。这时候,默认文件仍将被加载,只是当存在属性冲突时,使用环境的属性覆盖默认值。
有时在测试应用程序时,我们可能还需要使用不同的属性值。
Spring引导通过在测试运行期间查看src/test/resources目录来为我们处理这个问题。同样,默认属性仍然可以正常注入,但是如果发生冲突,默认属性将被覆盖。
如果我们需要对测试属性进行更细粒度的控制,那么我们可以使用@TestPropertySource注释。
这允许我们为特定的测试上下文设置测试属性,优先于默认的属性源:
@RunWith(SpringRunner.class)
@TestPropertySource("/foo.properties")
public class FilePropertyInjectionUnitTest {
@Value("${foo}")
private String foo;
@Test
public void whenFilePropertyProvided_thenProperlyInjected() {
assertThat(foo).isEqualTo("bar");
}
}
@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(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
export name=value
java -jar app.jar
@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通过类路径扫描查找和注册@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注释。
当我们想要将属性绑定到我们无法控制的第三方组件时,这种方法可能特别有用。
让我们创建一个简单的Item类,我们将在下一个例子中使用:
public class Item {
private String name;
private int size;
// standard getters and setters
}
现在让我们看看如何在@Bean方法上使用@ConfigurationProperties来将外部化的属性绑定到项目实例:
# 简单属性
[email protected]
item.size=32
@Configuration
public class ConfigProperties {
@Bean
@ConfigurationProperties(prefix = "item")
public Item item() {
return new Item();
}
}
因此,任何带有itme前缀的属性都将映射到Spring上下文管理的item实例中
@ConfigurationProperties使用JSR-303格式提供对属性的验证
@NotBlank
private String hostName;
@Length(max = 4, min = 1)
private String authMethod;
@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中我们有不同的访问属性的选项:
要在运行时更改文件中的属性,我们应该将该文件放在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检查一次。
如果我们想要重新加载通过环境实例加载的属性,我们必须扩展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");
}
}
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获得同样的效果,我们需要重新构造实例。
但是,Spring将只创建具有原型或请求范围的组件的新实例。
因此,我们重新加载环境的技术也适用于上述情况,但是对于单例,我们别无选择,只能实现一个端点来销毁和重新创建bean,或者在bean本身内部处理属性的重新加载。
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。
@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中获得新的值。
@Component
@RefreshScope
public class ValueRefreshConfigBean {
private String color;
public ValueRefreshConfigBean(@Value("${application.theme.color}") String color) {
this.color = color;
}
//put getter here
}
但是,有必要注意,/refresh不适用于具有显式单例作用域的bean