此文是 【Spring 容器详解】-> 【ApplicationContext 做了哪些企业化的增强?】的支节点。
Spring的配置属性绑定(Configuration Properties)是ApplicationContext提供的重要企业级功能,它允许将外部配置文件(如properties、YAML、环境变量、系统属性等)中的配置值绑定到Java对象上,实现类型安全的配置管理。
核心注解
1. @ConfigurationProperties
这是配置属性绑定的核心注解,用于将配置文件中的属性绑定到Java Bean上。
java
@ConfigurationProperties(prefix = "app")
@Component
public class AppProperties {
private String name;
private String version;
private Database database = new Database();
private Email email = new Email();
private List<String> features = new ArrayList<>();
private Map<String, String> metadata = new HashMap<>();
// getter和setter方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public Database getDatabase() { return database; }
public void setDatabase(Database database) { this.database = database; }
public Email getEmail() { return email; }
public void setEmail(Email email) { this.email = email; }
public List<String> getFeatures() { return features; }
public void setFeatures(List<String> features) { this.features = features; }
public Map<String, String> getMetadata() { return metadata; }
public void setMetadata(Map<String, String> metadata) { this.metadata = metadata; }
// 嵌套配置类
public static class Database {
private String url;
private String username;
private String password;
private int maxConnections = 10;
private boolean ssl = false;
// getter和setter方法
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getMaxConnections() { return maxConnections; }
public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; }
public boolean isSsl() { return ssl; }
public void setSsl(boolean ssl) { this.ssl = ssl; }
}
public static class Email {
private String host;
private int port = 587;
private String username;
private String password;
private boolean enableTls = true;
// getter和setter方法
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public boolean isEnableTls() { return enableTls; }
public void setEnableTls(boolean enableTls) { this.enableTls = enableTls; }
}
}
2. @EnableConfigurationProperties
启用配置属性绑定功能,通常在配置类上使用。
java
@Configuration
@EnableConfigurationProperties
public class AppConfig {
@Bean
public DataSource dataSource(AppProperties appProperties) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(appProperties.getDatabase().getUrl());
dataSource.setUsername(appProperties.getDatabase().getUsername());
dataSource.setPassword(appProperties.getDatabase().getPassword());
return dataSource;
}
@Bean
public JavaMailSender mailSender(AppProperties appProperties) {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(appProperties.getEmail().getHost());
mailSender.setPort(appProperties.getEmail().getPort());
mailSender.setUsername(appProperties.getEmail().getUsername());
mailSender.setPassword(appProperties.getEmail().getPassword());
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", appProperties.getEmail().isEnableTls());
return mailSender;
}
}
配置文件格式
1. Properties格式
properties
# application.properties
app.name=My Application
app.version=1.0.0
# 数据库配置
app.database.url=jdbc:mysql://localhost:3306/mydb
app.database.username=root
app.database.password=password
app.database.max-connections=20
app.database.ssl=true
# 邮件配置
app.email.host=smtp.gmail.com
app.email.port=587
app.email.username=myapp@gmail.com
app.email.password=mypassword
app.email.enable-tls=true
# 特性列表
app.features[0]=feature1
app.features[1]=feature2
app.features[2]=feature3
# 元数据
app.metadata.key1=value1
app.metadata.key2=value2
app.metadata.key3=value3
2. YAML格式
yaml
# application.yml
app:
name: My Application
version: 1.0.0
database:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
max-connections: 20
ssl: true
email:
host: smtp.gmail.com
port: 587
username: myapp@gmail.com
password: mypassword
enable-tls: true
features:
- feature1
- feature2
- feature3
metadata:
key1: value1
key2: value2
key3: value3
3. 环境变量格式
bash
# 环境变量设置
export APP_NAME="My Application"
export APP_VERSION="1.0.0"
export APP_DATABASE_URL="jdbc:mysql://localhost:3306/mydb"
export APP_DATABASE_USERNAME="root"
export APP_DATABASE_PASSWORD="password"
export APP_DATABASE_MAX_CONNECTIONS="20"
export APP_DATABASE_SSL="true"
export APP_EMAIL_HOST="smtp.gmail.com"
export APP_EMAIL_PORT="587"
export APP_EMAIL_USERNAME="myapp@gmail.com"
export APP_EMAIL_PASSWORD="mypassword"
export APP_EMAIL_ENABLE_TLS="true"
配置属性绑定机制
1. 属性名映射规则
Spring使用以下规则将配置文件中的属性名映射到Java属性:
java
@ConfigurationProperties(prefix = "app")
public class PropertyMappingExample {
// 配置文件: app.database-url -> Java属性: databaseUrl
private String databaseUrl;
// 配置文件: app.database_url -> Java属性: databaseUrl
private String databaseUrl;
// 配置文件: app.databaseUrl -> Java属性: databaseUrl
private String databaseUrl;
// 配置文件: app.database-url -> Java属性: databaseUrl
private String databaseUrl;
// 配置文件: app.database.url -> Java属性: database.url (嵌套对象)
private Database database;
// 配置文件: app.features[0] -> Java属性: features[0] (列表)
private List<String> features;
// 配置文件: app.metadata.key1 -> Java属性: metadata.key1 (Map)
private Map<String, String> metadata;
// getter和setter方法...
}
2. 类型转换
Spring自动进行类型转换,支持多种数据类型:
java
@ConfigurationProperties(prefix = "app")
public class TypeConversionExample {
// 字符串
private String name;
// 数字类型
private int port;
private long timeout;
private double ratio;
// 布尔类型
private boolean enabled;
// 日期类型
private LocalDate startDate;
private LocalDateTime lastModified;
// 枚举类型
private Environment environment;
// 集合类型
private List<String> tags;
private Set<Integer> ports;
private Map<String, Object> settings;
// 数组
private String[] aliases;
private int[] numbers;
// 自定义类型(需要自定义转换器)
private Duration timeout;
private DataSize maxSize;
// getter和setter方法...
}
public enum Environment {
DEV, TEST, PROD
}
3. 嵌套对象绑定
java
@ConfigurationProperties(prefix = "app")
public class NestedObjectExample {
private Database database;
private Email email;
private Cache cache;
public static class Database {
private String url;
private String username;
private String password;
private ConnectionPool connectionPool;
public static class ConnectionPool {
private int initialSize = 5;
private int maxSize = 20;
private int minIdle = 5;
private Duration maxWait = Duration.ofSeconds(30);
// getter和setter方法...
}
// getter和setter方法...
}
public static class Email {
private String host;
private int port;
private Authentication authentication;
public static class Authentication {
private String username;
private String password;
private String mechanism = "PLAIN";
// getter和setter方法...
}
// getter和setter方法...
}
public static class Cache {
private String type = "redis";
private Duration ttl = Duration.ofMinutes(30);
private Redis redis = new Redis();
public static class Redis {
private String host = "localhost";
private int port = 6379;
private String password;
private int database = 0;
// getter和setter方法...
}
// getter和setter方法...
}
// getter和setter方法...
}
配置属性验证
1. Bean Validation注解
java
@ConfigurationProperties(prefix = "app")
@Validated
public class ValidatedAppProperties {
@NotBlank(message = "应用名称不能为空")
private String name;
@Pattern(regexp = "\\d+\\.\\d+\\.\\d+", message = "版本号格式不正确")
private String version;
@Valid
private Database database;
@Valid
private Email email;
public static class Database {
@NotBlank(message = "数据库URL不能为空")
@Pattern(regexp = "jdbc:[a-zA-Z]+://.*", message = "数据库URL格式不正确")
private String url;
@NotBlank(message = "数据库用户名不能为空")
private String username;
@NotBlank(message = "数据库密码不能为空")
private String password;
@Min(value = 1, message = "最大连接数必须大于0")
@Max(value = 100, message = "最大连接数不能超过100")
private int maxConnections = 10;
// getter和setter方法...
}
public static class Email {
@NotBlank(message = "邮件服务器地址不能为空")
private String host;
@Min(value = 1, message = "端口号必须大于0")
@Max(value = 65535, message = "端口号不能超过65535")
private int port = 587;
@Email(message = "邮箱格式不正确")
private String username;
@NotBlank(message = "邮件密码不能为空")
private String password;
// getter和setter方法...
}
// getter和setter方法...
}
2. 自定义验证器
java
@ConfigurationProperties(prefix = "app")
@Validated
public class CustomValidatedProperties {
@ValidPort
private int port;
@ValidUrl
private String url;
@ValidDatabaseConnection
private Database database;
// getter和setter方法...
}
// 自定义端口验证注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PortValidator.class)
public @interface ValidPort {
String message() default "端口号无效";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 端口验证器实现
public class PortValidator implements ConstraintValidator<ValidPort, Integer> {
@Override
public boolean isValid(Integer port, ConstraintValidatorContext context) {
if (port == null) {
return false;
}
return port > 0 && port <= 65535;
}
}
// 自定义URL验证注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UrlValidator.class)
public @interface ValidUrl {
String message() default "URL格式无效";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// URL验证器实现
public class UrlValidator implements ConstraintValidator<ValidUrl, String> {
@Override
public boolean isValid(String url, ConstraintValidatorContext context) {
if (url == null || url.trim().isEmpty()) {
return false;
}
try {
new URL(url);
return true;
} catch (MalformedURLException e) {
return false;
}
}
}
// 自定义数据库连接验证注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DatabaseConnectionValidator.class)
public @interface ValidDatabaseConnection {
String message() default "数据库连接配置无效";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 数据库连接验证器实现
public class DatabaseConnectionValidator implements ConstraintValidator<ValidDatabaseConnection, Database> {
@Override
public boolean isValid(Database database, ConstraintValidatorContext context) {
if (database == null) {
return false;
}
// 验证数据库连接
try (Connection connection = DriverManager.getConnection(
database.getUrl(),
database.getUsername(),
database.getPassword())) {
return true;
} catch (SQLException e) {
return false;
}
}
}
配置属性元数据
1. 元数据文件
json
// META-INF/additional-spring-configuration-metadata.json
{
"properties": [
{
"name": "app.name",
"type": "java.lang.String",
"description": "应用程序名称",
"defaultValue": "My Application"
},
{
"name": "app.version",
"type": "java.lang.String",
"description": "应用程序版本号",
"pattern": "\\d+\\.\\d+\\.\\d+"
},
{
"name": "app.database.url",
"type": "java.lang.String",
"description": "数据库连接URL",
"pattern": "jdbc:[a-zA-Z]+://.*"
},
{
"name": "app.database.max-connections",
"type": "java.lang.Integer",
"description": "数据库最大连接数",
"minimum": 1,
"maximum": 100,
"defaultValue": 10
},
{
"name": "app.email.enable-tls",
"type": "java.lang.Boolean",
"description": "是否启用TLS加密",
"defaultValue": true
}
],
"hints": [
{
"name": "app.environment",
"values": [
{
"value": "dev",
"description": "开发环境"
},
{
"value": "test",
"description": "测试环境"
},
{
"value": "prod",
"description": "生产环境"
}
]
}
]
}
2. 元数据注解
java
@ConfigurationProperties(prefix = "app")
public class MetadataExample {
@ConfigurationProperty(description = "应用程序名称", defaultValue = "My Application")
private String name;
@ConfigurationProperty(description = "应用程序版本号")
@Pattern(regexp = "\\d+\\.\\d+\\.\\d+")
private String version;
@ConfigurationProperty(description = "数据库配置")
private Database database;
public static class Database {
@ConfigurationProperty(description = "数据库连接URL")
@Pattern(regexp = "jdbc:[a-zA-Z]+://.*")
private String url;
@ConfigurationProperty(description = "数据库最大连接数", defaultValue = "10")
@Min(1) @Max(100)
private int maxConnections;
// getter和setter方法...
}
// getter和setter方法...
}
配置属性绑定示例
1. 基本使用示例
java
@SpringBootApplication
@EnableConfigurationProperties
public class ConfigurationPropertiesApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigurationPropertiesApplication.class, args);
}
}
@Component
public class ConfigurationPropertiesExample {
@Autowired
private AppProperties appProperties;
@PostConstruct
public void printConfiguration() {
System.out.println("应用名称: " + appProperties.getName());
System.out.println("应用版本: " + appProperties.getVersion());
System.out.println("数据库配置:");
System.out.println(" URL: " + appProperties.getDatabase().getUrl());
System.out.println(" 用户名: " + appProperties.getDatabase().getUsername());
System.out.println(" 最大连接数: " + appProperties.getDatabase().getMaxConnections());
System.out.println(" SSL: " + appProperties.getDatabase().isSsl());
System.out.println("邮件配置:");
System.out.println(" 服务器: " + appProperties.getEmail().getHost());
System.out.println(" 端口: " + appProperties.getEmail().getPort());
System.out.println(" 用户名: " + appProperties.getEmail().getUsername());
System.out.println(" TLS: " + appProperties.getEmail().isEnableTls());
System.out.println("特性列表:");
for (String feature : appProperties.getFeatures()) {
System.out.println(" - " + feature);
}
System.out.println("元数据:");
for (Map.Entry<String, String> entry : appProperties.getMetadata().entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
}
}
2. 条件化配置
java
@Configuration
public class ConditionalConfiguration {
@Bean
@ConditionalOnProperty(name = "app.database.type", havingValue = "mysql")
public DataSource mysqlDataSource(AppProperties appProperties) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(appProperties.getDatabase().getUrl());
dataSource.setUsername(appProperties.getDatabase().getUsername());
dataSource.setPassword(appProperties.getDatabase().getPassword());
return dataSource;
}
@Bean
@ConditionalOnProperty(name = "app.database.type", havingValue = "h2")
public DataSource h2DataSource(AppProperties appProperties) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl(appProperties.getDatabase().getUrl());
dataSource.setUsername(appProperties.getDatabase().getUsername());
dataSource.setPassword(appProperties.getDatabase().getPassword());
return dataSource;
}
@Bean
@ConditionalOnProperty(name = "app.email.enable-tls", havingValue = "true")
public JavaMailSender tlsMailSender(AppProperties appProperties) {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(appProperties.getEmail().getHost());
mailSender.setPort(appProperties.getEmail().getPort());
mailSender.setUsername(appProperties.getEmail().getUsername());
mailSender.setPassword(appProperties.getEmail().getPassword());
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
return mailSender;
}
@Bean
@ConditionalOnProperty(name = "app.email.enable-tls", havingValue = "false", matchIfMissing = true)
public JavaMailSender plainMailSender(AppProperties appProperties) {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(appProperties.getEmail().getHost());
mailSender.setPort(appProperties.getEmail().getPort());
mailSender.setUsername(appProperties.getEmail().getUsername());
mailSender.setPassword(appProperties.getEmail().getPassword());
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
return mailSender;
}
}
3. 配置属性监听
java
@Component
public class ConfigurationChangeListener {
@EventListener
public void handleConfigurationChange(ConfigurationChangeEvent event) {
System.out.println("配置发生变化: " + event.getPropertyName());
System.out.println("旧值: " + event.getOldValue());
System.out.println("新值: " + event.getNewValue());
// 处理配置变化
if ("app.database.url".equals(event.getPropertyName())) {
// 重新初始化数据库连接
reinitializeDatabaseConnection();
}
}
private void reinitializeDatabaseConnection() {
System.out.println("重新初始化数据库连接...");
// 实现重新初始化逻辑
}
}
// 配置变化事件
public class ConfigurationChangeEvent {
private final String propertyName;
private final Object oldValue;
private final Object newValue;
public ConfigurationChangeEvent(String propertyName, Object oldValue, Object newValue) {
this.propertyName = propertyName;
this.oldValue = oldValue;
this.newValue = newValue;
}
// getter方法...
}
总结
Spring的配置属性绑定功能提供了强大的配置管理能力:
- 类型安全 - 配置值自动转换为Java类型
- 嵌套支持 - 支持复杂的嵌套配置结构
- 验证支持 - 集成Bean Validation进行配置验证
- 元数据支持 - 提供配置属性的描述和约束信息
这些特性使得Spring应用能够灵活地管理各种配置,为复杂的企业级应用提供了强大的配置管理支持。