配置属性绑定
深入理解SpringBoot配置属性的绑定机制,掌握@ConfigurationProperties的工作原理,理解属性绑定流程、类型转换、属性验证等完整过程。
📋 配置属性绑定概览
SpringBoot提供了强大的配置属性绑定机制,可以将配置文件中的属性自动绑定到Java对象。
🎯 一、@ConfigurationProperties注解
1.1 注解定义
java
@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;
String[] validators() default {};
}注解属性说明:
- prefix/value:配置属性的前缀
- ignoreInvalidFields:是否忽略无效字段(默认false)
- ignoreUnknownFields:是否忽略未知字段(默认true)
- validators:验证器类
1.2 基本使用
定义配置属性类:
java
@ConfigurationProperties(prefix = "my.app")
public class MyAppProperties {
private String name;
private String version;
private int port;
private boolean enabled;
// getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// ... 其他getter/setter
}配置文件:
yaml
# application.yml
my:
app:
name: MyApplication
version: 1.0.0
port: 8080
enabled: true启用配置属性:
java
@Configuration
@EnableConfigurationProperties(MyAppProperties.class)
public class MyAppConfiguration {
@Autowired
private MyAppProperties properties;
// 使用properties
}🎯 二、属性绑定流程
2.1 绑定流程概览
2.2 ConfigurationPropertiesBindingPostProcessor
ConfigurationPropertiesBindingPostProcessor负责处理配置属性绑定:
java
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, BeanFactoryAware, InitializingBean {
private BeanFactory beanFactory;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
bind(ConfigurationPropertiesBean.get(this.beanFactory, bean, beanName));
return bean;
}
private void bind(ConfigurationPropertiesBean propertiesBean) {
if (propertiesBean == null || hasBoundValueObject(propertiesBean.getName())) {
return;
}
Assert.state(propertiesBean.getBindMethod() == BindMethod.JAVA_BEAN,
"Cannot bind @ConfigurationProperties for bean '"
+ propertiesBean.getName() + "'. BindMethod is "
+ propertiesBean.getBindMethod() + ". "
+ "Must be 'java_bean' or have no setter.");
ConfigurationProperties annotation = propertiesBean.getAnnotation();
Bindable<?> target = propertiesBean.asBindTarget();
ConfigurationPropertySource source = getPropertySource(propertiesBean);
Binder binder = new Binder(source.withAliases(annotation));
ResolvableType type = propertiesBean.getBeanType();
BindResult<?> result = binder.bind(annotation.prefix(), target);
if (result.isBound()) {
Object value = result.get();
propertiesBean.getPropertyValues().forEach((name, propertyValue) -> {
PropertyDescriptor descriptor = BeanUtils
.getPropertyDescriptor(propertiesBean.getBeanType(), name);
if (descriptor != null) {
Method writeMethod = descriptor.getWriteMethod();
if (writeMethod != null) {
try {
Object convertedValue = convertValue(propertyValue,
writeMethod.getParameterTypes()[0]);
writeMethod.invoke(value, convertedValue);
} catch (Exception ex) {
throw new ConfigurationPropertiesBindException(
propertiesBean, propertiesBean.getAnnotation(),
ex);
}
}
}
});
}
}
}2.3 Binder类
Binder是属性绑定的核心类:
java
public class Binder {
public <T> BindResult<T> bind(String name, Bindable<T> target) {
return bind(ConfigurationPropertyName.of(name), target);
}
public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target) {
Context context = new Context();
BindHandler handler = getBindHandler(target);
T bound = bind(name, target, handler, context, false);
return BindResult.of(bound);
}
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
BindHandler handler, Context context, boolean allowRecursiveBinding) {
context.clearConfigurationProperty();
try {
target = handler.onStart(name, target, context);
if (target == null) {
return null;
}
Object bound = bindObject(name, target, handler, context,
allowRecursiveBinding);
return handler.onFinish(name, target, context, bound);
}
catch (Exception ex) {
return handler.onFailure(name, target, context, ex);
}
}
private <T> Object bindObject(ConfigurationPropertyName name,
Bindable<T> target, BindHandler handler, Context context,
boolean allowRecursiveBinding) {
// 绑定逻辑
// ...
}
}🎯 三、类型转换
3.1 ConversionService
Spring使用ConversionService进行类型转换:
java
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
<T> T convert(Object source, Class<T> targetType);
Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType);
}3.2 基本类型转换
Spring Boot自动支持以下类型转换:
- 基本类型:int, long, boolean, double等
- 包装类型:Integer, Long, Boolean, Double等
- 字符串:String
- 枚举:Enum
- 时间类型:Date, LocalDate, LocalDateTime等
示例:
yaml
my:
app:
port: 8080
enabled: true
timeout: 5000msjava
@ConfigurationProperties(prefix = "my.app")
public class MyAppProperties {
private int port; // 自动转换为int
private boolean enabled; // 自动转换为boolean
private Duration timeout; // 自动转换为Duration(5000ms)
}3.3 集合类型转换
List类型:
yaml
my:
app:
servers:
- server1
- server2
- server3java
@ConfigurationProperties(prefix = "my.app")
public class MyAppProperties {
private List<String> servers;
}Map类型:
yaml
my:
app:
config:
key1: value1
key2: value2java
@ConfigurationProperties(prefix = "my.app")
public class MyAppProperties {
private Map<String, String> config;
}3.4 嵌套对象转换
嵌套配置:
yaml
my:
app:
database:
url: jdbc:mysql://localhost:3306/test
username: root
password: password
pool:
max-size: 20
min-size: 5java
@ConfigurationProperties(prefix = "my.app")
public class MyAppProperties {
private Database database;
public static class Database {
private String url;
private String username;
private String password;
private Pool pool;
public static class Pool {
private int maxSize;
private int minSize;
}
}
}3.5 自定义类型转换器
创建转换器:
java
@Component
@ConfigurationPropertiesBinding
public class CustomConverter implements Converter<String, MyCustomType> {
@Override
public MyCustomType convert(String source) {
// 自定义转换逻辑
return new MyCustomType(source);
}
}🎯 四、属性验证
4.1 JSR-303验证
添加验证注解:
java
@ConfigurationProperties(prefix = "my.app")
@Validated
public class MyAppProperties {
@NotBlank
private String name;
@Min(1)
@Max(65535)
private int port;
@Email
private String email;
@NotNull
private Database database;
// getters and setters
}配置文件验证:
java
@Configuration
@EnableConfigurationProperties(MyAppProperties.class)
public class MyAppConfiguration {
public MyAppConfiguration(MyAppProperties properties) {
// 如果验证失败,会在应用启动时抛出异常
}
}4.2 自定义验证器
创建验证器:
java
public class MyAppPropertiesValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return MyAppProperties.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
MyAppProperties properties = (MyAppProperties) target;
if (properties.getPort() < 1024) {
errors.rejectValue("port", "port.invalid",
"Port must be >= 1024");
}
}
}使用验证器:
java
@ConfigurationProperties(prefix = "my.app")
@Validated
@ConfigurationPropertiesBinding
public class MyAppProperties {
// ...
}🎯 五、动态配置刷新
5.1 @RefreshScope
使用@RefreshScope实现配置的动态刷新(需要Spring Cloud):
java
@Component
@RefreshScope
@ConfigurationProperties(prefix = "my.app")
public class MyAppProperties {
// 配置修改后可以动态刷新
}5.2 ConfigurationPropertiesRebinder
手动触发配置重新绑定:
java
@Autowired
private ConfigurationPropertiesRebinder rebinder;
public void refreshProperties() {
rebinder.rebind("myAppProperties");
}📖 学习资源
视频教程
网站资源
✅ 学习检查清单
- [ ] 已理解@ConfigurationProperties的工作原理
- [ ] 已掌握属性绑定流程
- [ ] 已理解类型转换机制
- [ ] 已掌握集合类型绑定
- [ ] 已掌握嵌套对象绑定
- [ ] 已理解属性验证机制
- [ ] 已掌握自定义类型转换器
上一章节:条件注解机制
下一章节:ApplicationContext扩展
返回目录:SpringBoot源码深度解析