DataBinder 与 Bean 属性绑定体系
概述
DataBinder 是 Spring 中将外部属性值绑定到 Java Bean 属性上的核心设施。从 Spring MVC 的请求参数绑定到 Spring Boot 的 @ConfigurationProperties,底层都依赖 DataBinder 及其关联的 BeanWrapper、TypeConverterDelegate 等组件。
本文深入拆解 DataBinder 的完整绑定链路,涵盖属性访问策略、嵌套属性处理、类型转换查找、错误收集等 8 个细节点。
本文基于 Spring Framework 6.x / Spring Boot 3.x 源码分析。
1. 整体结构
DataBinder.bind(PropertyValues)
│
└─ DataBinder.doBind(MutablePropertyValues)
│
├─ DataBinder.applyDefaults() ← 空值默认值处理
│
├─ DataBinder.checkAllowedFields() ← 检查允许的字段
│
├─ DataBinder.checkRequiredFields() ← 检查必填字段
│
└─ AbstractPropertyAccessor.setPropertyValues()
│
└─ 逐个属性执行 setPropertyValue()
│
├─ 解析属性路径 ← "person.address.city"
│ └─ AbstractNestablePropertyAccessor
│ ├─ getPropertyAccessorForPropertyPath()
│ └─ getNestedPropertyAccessor()
│
├─ 获取当前属性的 TypeDescriptor ← 目标类型信息
│
├─ TypeConverterDelegate.convertIfNecessary() ← 类型转换
│
└─ 最终赋值: setter / field.set() ← 写入值2. DataBinder.bind(PropertyValues) 流程
2.1 源码
java
// DataBinder.java
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
// 绑定的目标对象
private final Object target;
// 绑定结果(收集错误信息)
private BindingResult bindingResult;
// 类型转换服务
private ConversionService conversionService;
// 消息编码前缀(用于国际化错误消息)
private String messageCodesPrefix;
/**
* 执行属性绑定 - 将提供的属性值绑定到 target 上
*/
public void bind(PropertyValues propertyValues) {
// 1. 转换为 MutablePropertyValues(支持增删改)
MutablePropertyValues mutablePropertyValues =
(propertyValues instanceof MutablePropertyValues
? (MutablePropertyValues) propertyValues
: new MutablePropertyValues(propertyValues));
// 2. 执行绑定
doBind(mutablePropertyValues);
}
protected void doBind(MutablePropertyValues mutablePropertyValues) {
// 1. 应用默认值(如有)
applyDefaults(mutablePropertyValues);
// 2. 检查允许的字段白名单
checkAllowedFields(mutablePropertyValues);
// 3. 检查必填字段
checkRequiredFields(mutablePropertyValues);
// 4. 执行实际的属性设置
// 委托给 getInternalAccessor() —— 返回 BeanWrapperImpl
getInternalAccessor().setPropertyValues(mutablePropertyValues);
}
// 获取内部的属性访问器
private AbstractPropertyAccessor getInternalAccessor() {
// DataBinder 内部维护了一个 BeanWrapperImpl
return (AbstractPropertyAccessor) getBindingResult()
.getPropertyAccessor();
}
}2.2 完整调用链
客户端调用: dataBinder.bind(propertyValues)
│
├─ new MutablePropertyValues() ← 确保可变
│
└─ doBind(mutablePropertyValues)
│
├─ 1. applyDefaults()
│ └─ 设置 @Value 的默认值
│
├─ 2. checkAllowedFields()
│ └─ 只允许在 allowedFields 列表中的字段
│
├─ 3. checkRequiredFields()
│ └─ requiredFields 中的字段必须非空
│
└─ 4. BeanWrapperImpl.setPropertyValues()
│
├─ 逐个遍历 PropertyValue
│ └─ Person(name=John, address.city=Shanghai)
│
└─ setPropertyValue(PropertyValue)
└─ 详见 §32.3 在 Spring MVC 中的使用
java
// ServletRequestDataBinder.java —— Spring MVC 中绑定请求参数
public class ServletRequestDataBinder extends WebDataBinder {
public void bind(ServletRequest request) {
// 将 HTTP 请求参数转换为 MutablePropertyValues
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
// 调用父类 DataBinder.bind()
bind(mpvs);
}
}3. BeanWrapperImpl 的 3 种属性访问策略
3.1 源码
java
// BeanWrapperImpl.java
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor
implements BeanWrapper {
// 属性访问器的三种实现
/** 策略 1:通过 MethodInterceptor(CGLIB 代理) */
private CglibPropertyAccessor cglibPropertyAccessor;
/** 策略 2:通过 FieldInterceptor(直接字段反射) */
private DirectFieldAccessor directFieldAccessor;
/** 策略 3:通过 Method(getter/setter 反射) */
private PropertyDescriptor[] propertyDescriptors;
}3.2 三种策略的源码
java
// BeanWrapperImpl.java
private PropertyHandler getLocalPropertyHandler(String propertyName) {
// 1. 优先从 CachedIntrospectionResults 中获取 PropertyDescriptor
PropertyDescriptor pd = getCachedIntrospectionResults()
.getPropertyDescriptor(propertyName);
if (pd != null && pd.getWriteMethod() != null) {
// 策略 1: 使用 MethodInterceptor(CGLIB 优化)
// 当 BeanWrapperImpl 被 CGLIB 代理时,使用 MethodInterceptor
if (this.cglibPropertyAccessor != null) {
return this.cglibPropertyAccessor
.getPropertyHandler(propertyName);
}
// 策略 3: 使用 Method(标准的 getter/setter 反射)
return new BeanPropertyHandler(pd);
}
// 策略 2: 没有 setter → 尝试直接字段反射
// 适用于没有 setter 的公开字段
if (this.directFieldAccessor != null) {
return this.directFieldAccessor
.getPropertyHandler(propertyName);
}
return null; // 找不到该属性的处理器
}3.3 三种策略的对比
| 策略 | 实现类 | 访问方式 | 适用场景 |
|---|---|---|---|
| MethodInterceptor | CglibPropertyAccessor | CGLIB 代理调用 setter | Bean 被 CGLIB 代理时 |
| FieldInterceptor | DirectFieldAccessor | Field.set() 直接赋值 | 公开字段、无 setter 的字段 |
| Method | BeanPropertyHandler | Method.invoke(setter) | 标准 Java Bean(最常见) |
3.4 BeanPropertyHandler 的源码
java
// BeanWrapperImpl.java 内部类
private class BeanPropertyHandler extends PropertyHandler {
private final PropertyDescriptor pd;
private final Method readMethod;
private final Method writeMethod;
public BeanPropertyHandler(PropertyDescriptor pd) {
super(pd.getPropertyType(), pd.getReadMethod() != null
&& pd.getReadMethod().getDeclaringClass() == Object.class);
this.pd = pd;
this.readMethod = pd.getReadMethod();
this.writeMethod = pd.getWriteMethod();
}
@Override
@Nullable
public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(
new MethodParameter(this.readMethod, -1));
}
@Override
public void setValue(@Nullable Object value) throws Exception {
// 通过反射调用 setter 方法
// 例如: setUserName("John") → person.setUserName("John")
Method writeMethod = this.pd.getWriteMethod();
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(getWrappedInstance(), value);
}
@Override
@Nullable
public Object getValue() throws Exception {
// 通过反射调用 getter 方法
Method readMethod = this.pd.getReadMethod();
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
return readMethod.invoke(getWrappedInstance());
}
}4. BeanWrapperImpl.setPropertyValue() 的嵌套属性处理
4.1 嵌套属性路径解析
java
// AbstractNestablePropertyAccessor.java
public abstract class AbstractNestablePropertyAccessor
extends AbstractPropertyAccessor {
// 处理属性路径分隔符
private static final char NESTED_PROPERTY_SEPARATOR = '.';
// 处理带索引的属性路径
private static final char PROPERTY_KEY_PREFIX = '[';
private static final char PROPERTY_KEY_SUFFIX = ']';
@Override
public void setPropertyValue(String propertyName, Object value)
throws BeansException {
// 解析嵌套路径
// example: "person.address.city"
// → ["person", "address", "city"]
AbstractNestablePropertyAccessor nestedPa =
getPropertyAccessorForPropertyPath(propertyName);
// 获取最后一个属性名
String lastPropertyName =
getFinalPath(propertyName).getPath();
// 执行赋值
nestedPa.setLocalPropertyValue(lastPropertyName, value, true);
}
}4.2 嵌套路径解析源码
java
// AbstractNestablePropertyAccessor.java
protected AbstractNestablePropertyAccessor
getPropertyAccessorForPropertyPath(String propertyPath) {
// 1. 查找第一个分隔符的位置
int dotIndex = propertyPath.indexOf(NESTED_PROPERTY_SEPARATOR);
int keyIndex = propertyPath.indexOf(PROPERTY_KEY_PREFIX);
if (dotIndex < 0 && keyIndex < 0) {
// 没有分隔符 → 直接返回当前访问器
return this;
}
// 2. 取出第一段属性名
String firstPropertyName;
if (dotIndex >= 0 && (keyIndex < 0 || dotIndex < keyIndex)) {
// "person.address.city" → first = "person"
firstPropertyName = propertyPath.substring(0, dotIndex);
} else {
// "addresses[0].city" → first = "addresses[0]"
firstPropertyName = propertyPath.substring(0, keyIndex);
}
// 3. 递归处理剩余路径
String remainingPath = propertyPath.substring(
firstPropertyName.length() + 1);
// 4. 获取第一段属性的值(中间对象)
Object value = getPropertyValue(firstPropertyName);
if (value == null) {
// 5. 中间对象为空 → 自动创建
// 例如: person.address = null → new Address()
value = createDefaultPropertyValue(
firstPropertyName, remainingPath);
}
// 6. 为中间对象创建新的 BeanWrapperImpl
AbstractNestablePropertyAccessor nestedPa =
newNestedPropertyAccessor(value);
// 7. 递归处理剩余路径
return nestedPa.getPropertyAccessorForPropertyPath(remainingPath);
}4.3 嵌套属性自动创建示例
java
public class Person {
private String name;
private Address address; // ← 初始为 null
// getter / setter
}
public class Address {
private String city;
private String street;
// getter / setter
}
// 绑定时传入: person.address.city = "Shanghai"
// BeanWrapperImpl 自动:
// 1. 检测到 address 为 null
// 2. 通过 Address 的无参构造器创建 Address 实例
// 3. 调用 setAddress(new Address())
// 4. 再对 address.city 赋值 "Shanghai"4.4 自动创建源码
java
// AbstractNestablePropertyAccessor.java
@Nullable
private Object createDefaultPropertyValue(
String propertyName, String remainingPath) {
// 获取属性类型
Class<?> propertyType = getPropertyType(propertyName);
if (propertyType == null || propertyType.isInterface()
|| propertyType.isArray() || propertyType.isEnum()) {
return null; // 无法创建
}
try {
// 通过无参构造器创建实例
Object defaultValue = BeanUtils.instantiateClass(propertyType);
// 设置到当前对象
setPropertyValue(propertyName, defaultValue);
return defaultValue;
} catch (Exception ex) {
return null; // 创建失败(如没有无参构造器)
}
}5. TypeConverterDelegate.convertIfNecessary() 的 4 步查找
5.1 源码
java
// TypeConverterDelegate.java
@Nullable
public <T> T convertIfNecessary(@Nullable String propertyName,
@Nullable Object oldValue, @Nullable Object newValue,
Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor)
throws TypeMismatchException {
// 情况 1: 类型兼容 → 直接返回
if (requiredType == null
|| (newValue != null && requiredType.isInstance(newValue))) {
return (T) newValue;
}
// 情况 2: null 值处理
if (newValue == null) {
return null;
}
// 步骤 1: 查找自定义 PropertyEditor
// @InitBinder 或直接注册的 PropertyEditor
PropertyEditor editor = this.propertyEditorRegistry
.findCustomEditor(requiredType, propertyName);
if (editor != null) {
editor.setValue(newValue instanceof String && !(editor instanceof CustomEditorWithStringConversion)
? newValue : newValue);
Object result = editor.getValue();
if (result != null) return (T) result;
}
// 步骤 2: 使用 ConversionService
if (this.conversionService != null) {
// 检查是否可以转换
if (this.conversionService.canConvert(
typeDescriptor != null
? typeDescriptor : TypeDescriptor.forObject(newValue),
TypeDescriptor.valueOf(requiredType))) {
try {
return (T) this.conversionService.convert(
newValue,
typeDescriptor != null
? typeDescriptor : TypeDescriptor.forObject(newValue),
TypeDescriptor.valueOf(requiredType));
} catch (ConversionException ex) {
throw new TypeMismatchException(...);
}
}
}
// 步骤 3: 尝试通过 String 构造器或静态工厂方法创建
if (newValue instanceof String) {
// 3a: 查找目标类型的无参构造器并调用 setAsText?
// 3b: 查找 String 参数的静态工厂方法
// 3c: 查找 String 参数的构造器
T result = (T) BeanUtils.instantiateUsingStringArg(requiredType, newValue);
if (result != null) return result;
}
// 步骤 4: 通过 Java Beans PropertyEditor 兜底
PropertyEditor editor = this.propertyEditorRegistry
.getDefaultEditor(requiredType);
if (editor == null) {
editor = PropertyEditorManager.findEditor(requiredType);
}
if (editor != null) {
editor.setAsText((String) newValue);
return (T) editor.getValue();
}
// 全部失败 → 抛出异常
throw new TypeMismatchException(...);
}5.2 4 步查找图示
convertIfNecessary("age", "25", Integer.class)
│
├─ Step 1: 自定义 PropertyEditor
│ findCustomEditor(Integer.class, "age")
│ → null(未注册自定义 Editor)
│
├─ Step 2: ConversionService
│ canConvert(String.class, Integer.class)
│ → true(DefaultConversionService 注册了 StringToIntegerConverter)
│ → convert("25", Integer.class) = 25
│ → return 25 ✅
│
├─ Step 3: String 构造器/工厂方法(Step 2 已命中,不执行)
│
└─ Step 4: 默认 PropertyEditor(Step 2 已命中,不执行)5.3 各步骤负责的转换类型
| 步骤 | 负责内容 | 示例 |
|---|---|---|
| 1. 自定义 PropertyEditor | 用户通过 @InitBinder 注册的转换器 | DateEditor 自定义日期格式 |
| 2. ConversionService | Spring 80+ 内置转换器 + Boot 扩展 | String → Integer, String → Duration |
| 3. 构造器/工厂方法 | 目标类有 String 参数构造器或 valueOf() | new Integer("25"), Boolean.valueOf("true") |
| 4. 默认 PropertyEditor | Java Beans 标准 Editor | java.util.Date → DateEditor |
6. DirectFieldAccessor vs BeanWrapperImpl
6.1 对比
| 特性 | BeanWrapperImpl | DirectFieldAccessor |
|---|---|---|
| 访问方式 | Setter/Getter 方法 | Field.set() / Field.get() |
| 是否调用 setter | ✅ 是 | ❌ 否 |
| 触发 setter 副作用 | ✅ 会(如验证、事件) | ❌ 不会 |
| 支持非公开 setter | ❌ 需要公开 setter | ✅ 可以访问私有字段 |
| 自动嵌套属性创建 | ✅ 支持 | ✅ 支持 |
| 通过类型转换 | ✅ 支持 | ✅ 支持 |
| 默认验证器绑定 | ✅ 支持 | ✅ 支持 |
| 性能 | 稍慢(反射调用方法) | 稍快(直接字段访问) |
| 适用场景 | 标准 Java Bean | 只有字段无 setter 的对象 |
6.2 DirectFieldAccessor 的使用
java
// DirectFieldAccessor.java
public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
@Override
@Nullable
protected PropertyHandler getLocalPropertyHandler(String propertyName) {
// 直接通过 Field 反射访问
Field field = findField(propertyName);
if (field != null) {
return new DirectFieldPropertyHandler(field);
}
return null;
}
// 内部类:通过 Field.set/get 直接操作字段
private class DirectFieldPropertyHandler extends PropertyHandler {
private final Field field;
@Override
public void setValue(@Nullable Object value) throws Exception {
// 直接设置字段值,不调用 setter
ReflectUtils.makeAccessible(this.field);
this.field.set(getWrappedInstance(), value);
}
@Override
@Nullable
public Object getValue() throws Exception {
ReflectUtils.makeAccessible(this.field);
return this.field.get(getWrappedInstance());
}
}
}6.3 在 Spring MVC 中的选择
java
// DataBinder.java
public void initBeanPropertyAccess() {
// 默认使用 BeanWrapperImpl(基于 setter)
getBindingResult().initBeanPropertyAccess();
}
public void initDirectFieldAccess() {
// 切换为 DirectFieldAccessor(基于字段)
getBindingResult().initDirectFieldAccess();
}java
// 在 Controller 中选择
@InitBinder
public void initBinder(WebDataBinder binder) {
// 使用 DirectFieldAccessor(字段直接访问)
binder.initDirectFieldAccess();
// 适用于只有字段没有 setter 的 POJO
}7. BeanPropertyBindingResult 的错误收集
7.1 源码
java
// BeanPropertyBindingResult.java
public class BeanPropertyBindingResult extends AbstractPropertyBindingResult {
// 存储所有绑定错误的列表
@Nullable
private final List<ObjectError> errors;
public BeanPropertyBindingResult(
Object target, String objectName) {
super(objectName);
this.target = target;
// 懒加载错误列表
this.errors = new ArrayList<>();
}
@Override
public void addError(ObjectError error) {
// 添加错误
this.errors.add(error);
}
@Override
public void rejectValue(@Nullable String field,
String errorCode, @Nullable Object[] errorArgs,
@Nullable String defaultMessage) {
// 创建 FieldError(字段级别错误)
FieldError fe = new FieldError(
getObjectName(), // 对象名(如 "person")
field, // 字段名(如 "age")
null, // 被拒绝的值
false, // 是否绑定失败
new String[] { errorCode }, // 错误码
errorArgs, // 错误参数
defaultMessage); // 默认消息
addError(fe);
}
@Override
public void reject(String errorCode,
@Nullable Object[] errorArgs,
@Nullable String defaultMessage) {
// 创建 ObjectError(全局级别错误)
ObjectError oe = new ObjectError(
getObjectName(),
new String[] { errorCode },
errorArgs,
defaultMessage);
addError(oe);
}
}7.2 错误类型
java
// ObjectError —— 全局错误(与具体字段无关)
ObjectError: "person" - "年龄不能为负数"
// FieldError —— 字段级别错误(绑定到具体字段)
FieldError: "person.age" - "年龄不能小于0"7.3 错误收集的调用链
java
// 类型转换失败时的错误收集
TypeConverterDelegate.convertIfNecessary(...)
→ 转换失败抛出 TypeMismatchException
→ AbstractPropertyAccessor.setPropertyValue() 捕获异常
→ processKeyedProperty() 或 setLocalPropertyValue() 调用
→ BeanPropertyBindingResult.rejectValue("age", "typeMismatch")
→ 创建 FieldError 添加到 errors 列表7.4 在 Spring MVC 中的使用
java
@PostMapping("/user")
public String createUser(@Valid @ModelAttribute User user,
BindingResult result) {
if (result.hasErrors()) {
// result.getAllErrors() → 包含所有 FieldError 和 ObjectError
// result.getFieldErrors() → 只获取字段级别的错误
// result.getFieldError("age") → 获取 age 字段的错误
for (FieldError error : result.getFieldErrors()) {
String field = error.getField(); // "age"
String message = error.getDefaultMessage(); // "must be between 0 and 150"
Object rejected = error.getRejectedValue(); // 被拒绝的值
}
return "error";
}
return "success";
}8. Spring Boot 中 DataBinder 的典型使用
8.1 @ConfigurationProperties 底层的 Binder
Spring Boot 的 @ConfigurationProperties 并不直接使用 DataBinder,而是使用 Binder API。但 Binder 内部仍然使用 BeanWrapper 来设置属性:
java
// Binder.java 内部 (简化)
private <T> T bindObject(String name, Bindable<T> target, BindContext context) {
// 1. 获取目标类型
Class<?> resolvedType = target.getType().resolve();
if (resolvedType == null) {
return null;
}
// 2. 创建 BeanWrapperImpl
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(resolvedType);
// 3. 设置 ConversionService(使用 ApplicationConversionService)
beanWrapper.setConversionService(this.conversionService);
// 4. 绑定属性
for (ConfigurationPropertySource source : this.propertySources) {
// 查找匹配当前 prefix 的配置项
ConfigurationProperty property = source
.getConfigurationProperty(name);
if (property != null) {
// 5. 使用 BeanWrapper 设置属性值
beanWrapper.setPropertyValue(
property.getName(),
property.getValue());
}
}
// 6. 返回绑定的对象
return (T) beanWrapper.getWrappedInstance();
}8.2 各组件在 Boot 中的对应关系
| Spring 标准组件 | Spring Boot 中的使用 | 作用 |
|---|---|---|
DataBinder | Binder 内部未直接使用 | Boot 自己实现了 Binder API |
BeanWrapperImpl | Binder 在绑定过程中内部使用 | 为 @ConfigurationProperties 的 POJO 设值 |
TypeConverterDelegate | Binder 通过 ConversionService 转换 | 类型转换 |
ConversionService | ApplicationConversionService | 支持 Duration/DataSize 等 Boot 特有类型 |
BeanPropertyBindingResult | BindResult / BindException | 错误收集 |
8.3 从 Binder 到 BeanWrapper 的调用路径
Binder.bind("spring.datasource", Bindable.ofInstance(target))
│
└─ bindObject("spring.datasource", target)
│
├─ 创建 BeanWrapperImpl(target)
│
├─ beanWrapper.setConversionService(ApplicationConversionService)
│
├─ 遍历 PropertySource 查找 "spring.datasource.url" / "spring.datasource.username" 等
│
├─ 调用 beanWrapper.setPropertyValue("url", "jdbc:mysql://...")
│ └─ BeanWrapperImpl.setPropertyValue()
│ └─ TypeConverterDelegate.convertIfNecessary()
│ └─ ConversionService.convert()
│
└─ 返回绑定的 DataSourceProperties 实例9. MutablePropertyValues 的可变性
9.1 源码
java
// MutablePropertyValues.java
public class MutablePropertyValues implements PropertyValues, Serializable {
private final List<PropertyValue> propertyValueList;
public MutablePropertyValues() {
this.propertyValueList = new ArrayList<>();
}
public MutablePropertyValues(@Nullable PropertyValues original) {
// 从其他 PropertyValues 复制
if (original != null) {
this.propertyValueList = new ArrayList<>(
original.getPropertyValueList().size());
for (PropertyValue pv : original.getPropertyValueList()) {
this.propertyValueList.add(new PropertyValue(pv));
}
} else {
this.propertyValueList = new ArrayList<>();
}
}
// 添加属性值
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
// 如果已存在同名属性 → 覆盖
for (int i = 0; i < this.propertyValueList.size(); i++) {
PropertyValue current = this.propertyValueList.get(i);
if (current.getName().equals(pv.getName())) {
this.propertyValueList.set(i, pv); // 覆盖
return this;
}
}
this.propertyValueList.add(pv); // 新增
return this;
}
// 便捷方法:直接通过名称 + 值添加
public MutablePropertyValues add(String propertyName, @Nullable Object propertyValue) {
addPropertyValue(new PropertyValue(propertyName, propertyValue));
return this;
}
// 移除属性值
public MutablePropertyValues removePropertyValue(String propertyName) {
this.propertyValueList.removeIf(
pv -> pv.getName().equals(propertyName));
return this;
}
// 修改属性值
public MutablePropertyValues set(String propertyName,
@Nullable Object propertyValue) {
// 与 add 语义相同——不存在则新增,存在则覆盖
return add(propertyName, propertyValue);
}
@Override
public PropertyValue[] getPropertyValues() {
return this.propertyValueList.toArray(new PropertyValue[0]);
}
@Override
public boolean contains(String propertyName) {
return this.propertyValueList.stream()
.anyMatch(pv -> pv.getName().equals(propertyName));
}
@Override
@Nullable
public PropertyValue getPropertyValue(String propertyName) {
for (PropertyValue pv : this.propertyValueList) {
if (pv.getName().equals(propertyName)) {
return pv;
}
}
return null;
}
}9.2 可变性的使用
java
// 创建空的 MutablePropertyValues
MutablePropertyValues mpvs = new MutablePropertyValues();
// 1. 添加属性
mpvs.add("name", "John");
mpvs.add("age", 25);
// 2. 覆盖已有属性
mpvs.add("age", 30); // age 从 25 → 30
// 3. 链式调用
mpvs.add("email", "john@example.com")
.add("phone", "1234567890");
// 4. 在 DataBinder 中使用
DataBinder binder = new DataBinder(person);
binder.bind(mpvs); // 所有属性绑定到 person 对象
// 5. 动态移除属性
mpvs.removePropertyValue("phone"); // phone 将不会被绑定
// 6. 检查是否包含
if (mpvs.contains("name")) {
PropertyValue pv = mpvs.getPropertyValue("name");
// pv.getValue() = "John"
}
// 7. 转换为不可变
PropertyValues immutable = new PropertyValues<>() {
// ... 只读实现
};总结
| # | 细节点 | 核心要点 |
|---|---|---|
| ① | DataBinder.bind(PropertyValues) 流程 | doBind() → applyDefaults() → checkAllowedFields() → checkRequiredFields() → BeanWrapper.setPropertyValues() |
| ② | BeanWrapperImpl 3 种属性访问 | MethodInterceptor(CGLIB) / FieldInterceptor(字段直接) / Method(getter/setter) |
| ③ | 嵌套属性处理 | 按 . 分隔递归调用 getPropertyAccessorForPropertyPath(),中间对象为 null 时自动通过无参构造器创建 |
| ④ | TypeConverterDelegate 4 步查找 | 自定义 PropertyEditor → ConversionService → String 构造器/工厂方法 → 默认 PropertyEditor |
| ⑤ | DirectFieldAccessor vs BeanWrapperImpl | 字段直接赋值 vs setter 方法赋值,后者会触发验证/事件等副作用 |
| ⑥ | BeanPropertyBindingResult 错误收集 | FieldError(字段级) / ObjectError(全局级),通过 rejectValue()/reject() 添加 |
| ⑦ | Spring Boot 典型使用 | Binder 内部使用 BeanWrapperImpl + ApplicationConversionService 为 @ConfigurationProperties 绑定值 |
| ⑧ | MutablePropertyValues 可变性 | add() 覆盖/新增、removePropertyValue() 删除、set() 修改,链式调用 |