springmvc–数据绑定器WebDataBinder
文章目录
- springmvc--数据绑定器`WebDataBinder`
- 1 简单介绍
- 2 `ServletRequestDataBinderFactory`
- 3 `ConfigurableWebBindingInitializer`
- 4 `ExtendedServletRequestDataBinder`
- 5 `BeanPropertyBindingResult`
- 6 `ServletRequestParameterPropertyValues`
1 简单介绍
数据绑定不是springmvc
所特有的,早在spring
的时候就已经存在了,当时是DataBinder
,后来springmvc
在其基础上扩展,并广泛应用。数据绑定器可以完成如下功能
- 类型转换
- 数据校验
- 属性填充
接下来我们就来看一下它是如何完成这三大功能的
2 ServletRequestDataBinderFactory
类图如下
它的顶层接口是
WebDataBinderFactory
,我们先看一下这个接口
public interface WebDataBinderFactory {
/**
* Create a {@link WebDataBinder} for the given object.
* @param webRequest the current request
* @param target the object to create a data binder for,
* or {@code null} if creating a binder for a simple type
* @param objectName the name of the target object
* @return the created {@link WebDataBinder} instance, never null
* @throws Exception raised if the creation and initialization of the data binder fails
*/
WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName)
throws Exception;
}
只有一个方法,用来创建一个
WebDataBinder
数据绑定器对象
2.1 构造方法
/**
* initializer默认为ConfigurableWebBindingInitializer
* springmvc配置文件中使用`<mvc:annotation-driven>`,会通过属性填充给它一个默认值
*/
public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods,
@Nullable WebBindingInitializer initializer) {
super(binderMethods, initializer);
}
父类InitBinderDataBinderFactory
构造方法
//保存有@InitBinder注解的方法
private final List<InvocableHandlerMethod> binderMethods;
/**
* Create a new InitBinderDataBinderFactory instance.
* @param binderMethods {@code @InitBinder} methods
* @param initializer for global data binder initialization
*/
public InitBinderDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods,
@Nullable WebBindingInitializer initializer) {
super(initializer);
this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());
}
父类DefaultDataBinderFactory
构造方法
/**
* 绑定器的初始化器
* 使用这个初始化器来初始化绑定器
*/
@Nullable
private final WebBindingInitializer initializer;
/**
* Create a new {@code DefaultDataBinderFactory} instance.
* @param initializer for global data binder initialization
* (or {@code null} if none)
*/
public DefaultDataBinderFactory(@Nullable WebBindingInitializer initializer) {
this.initializer = initializer;
}
构造方法只是简单的将两个参数对象赋值给属性
2.2 createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName)
方法,创建属性对应的数据绑定器
/**
* Create a new {@link WebDataBinder} for the given target object and
* initialize it through a {@link WebBindingInitializer}.
* @throws Exception in case of invalid state or arguments
*/
@Override
@SuppressWarnings("deprecation")
public final WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
//创建数据绑定器实例,见2.2.1
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
//初始化器初始化数据绑定器,见3.1
if (this.initializer != null) {
this.initializer.initBinder(dataBinder, webRequest);
}
//初始化数据绑定器,见2.2.2
initBinder(dataBinder, webRequest);
return dataBinder;
}
2.2.1 创建数据绑定器实例
/**
* Returns an instance of {@link ExtendedServletRequestDataBinder}.
*/
@Override
protected ServletRequestDataBinder createBinderInstance(
@Nullable Object target, String objectName, NativeWebRequest request) throws Exception {
/**
* 创建一个数据绑定器,见4.1
* 实际类型是ExtendedServletRequestDataBinder
*/
return new ExtendedServletRequestDataBinder(target, objectName);
}
2.2.2 初始化数据绑定器
/**
* Initialize a WebDataBinder with {@code @InitBinder} methods.
* <p>If the {@code @InitBinder} annotation specifies attributes names,
* it is invoked only if the names include the target object name.
* @throws Exception if one of the invoked @{@link InitBinder} methods fails
* @see #isBinderMethodApplicable
*/
@Override
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
//遍历所有的@InitBinder注解的方法
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
//过滤掉不符合条件的方法
if (isBinderMethodApplicable(binderMethod, dataBinder)) {
/**
* 反射执行@InitBinder注解的方法
* 方法源码内容在springmvc--7--ServletInvocableHandlerMethod增强的处理器方法
*/
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
if (returnValue != null) {
throw new IllegalStateException(
"@InitBinder methods must not return a value (should be void): " + binderMethod);
}
}
}
}
过滤掉不符合条件的方法
/**
* Determine whether the given {@code @InitBinder} method should be used
* to initialize the given {@link WebDataBinder} instance. By default we
* check the specified attribute names in the annotation value, if any.
*/
protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
//获取方法上@InitBinder注解信息
InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
Assert.state(ann != null, "No InitBinder annotation");
String[] names = ann.value();
/**
* 如果注解未指定value属性值,则表示所有的绑定器都要执行该方法进行初始化
* 如果指定了,那么就会判断要绑定参数的参数名,只有和注解上指定的匹配,才会执行
* 该注解方法进行初始化
* getObjectName()方法,获取要绑定参数的参数名,见4.2.2
*/
return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
}
遍历所有标注
@InitBinder
注解的方法,逐个过滤,判定通过之后才会执行方法
- 如果注解未指定
value
属性值,判定通过- 如果注解指定了
value
属性值,那么就判断要绑定参数的参数名,只有和注解上指定的匹配,才会判定通过
3 ConfigurableWebBindingInitializer
这是
springmvc
默认的初始化器,它继承了WebBindingInitializer
接口
public interface WebBindingInitializer {
/**
* 初始化数据绑定器,5.0以后使用这个方法
* @since 5.0
*/
void initBinder(WebDataBinder binder);
/**
* 该方法已经被废弃了,但是在springmvc5.2中还未修改过来,于是
* 通过默认实现回调5.0新增的接口方法
*/
@Deprecated
default void initBinder(WebDataBinder binder, WebRequest request) {
initBinder(binder);
}
}
我们来看看ConfigurableWebBindingInitializer
类的属性
public class ConfigurableWebBindingInitializer implements WebBindingInitializer {
//是否开启处理嵌套属性
private boolean autoGrowNestedPaths = true;
private boolean directFieldAccess = false;
@Nullable
private MessageCodesResolver messageCodesResolver;
@Nullable
private BindingErrorProcessor bindingErrorProcessor;
//数据校验器
@Nullable
private Validator validator;
//统一转换服务
@Nullable
private ConversionService conversionService;
//属性注册器注册商
@Nullable
private PropertyEditorRegistrar[] propertyEditorRegistrars;
}
3.1 initBinder(WebDataBinder binder)
方法,初始化属性绑定器
/**
* Initialize the given DataBinder for the given (Servlet) request.
* @param binder the DataBinder to initialize
* @param request the web request that the data binding happens within
* @deprecated as of 5.0 in favor of {@link #initBinder(WebDataBinder)}
*/
@Deprecated
default void initBinder(WebDataBinder binder, WebRequest request) {
initBinder(binder);
}
上面这个方法在
5.0
之后就被废弃掉了,该方法里面回调了initBinder(binder)
方法,源码如下所示
@Override
public void initBinder(WebDataBinder binder) {
//设置处理嵌套属性,见4.2.1
binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
/**
* 直接通过字段绑定属性,而不是setter方法,见4.4
*/
if (this.directFieldAccess) {
binder.initDirectFieldAccess();
}
//消息码解析器
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
//绑定错误处理器
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
//数据校验器
if (this.validator != null && binder.getTarget() != null &&
this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator);
}
//统一转换服务
if (this.conversionService != null) {
binder.setConversionService(this.conversionService);
}
//属性编辑器注册器商,批量注册属性编辑器到数据绑定器中
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
propertyEditorRegistrar.registerCustomEditors(binder);
}
}
}
实际上就是将用户在初始化器上的配置覆盖到数据绑定器中
4 ExtendedServletRequestDataBinder
这是
springmvc
默认使用的数据绑定器DataBinder
,见2.2.1
它的类图如下所示
在学习
spring
的类型转换时,我们就接触过了TypeConverter
接口和PropertyEditorRegistry
接口,我们熟知的BeanWrapperImpl
也实现了这两个接口
TypeConverter
接口:spring
属性填充阶段,调用该接口的convertForProperty(value, propertyName)
方法完成该属性的类型转换PropertyEditorRegistry
接口:PropertyEditor
注册中心,提供注册和查找指定转换类型的PropertyEditor
方法
下面是ExtendedServletRequestDataBinder
和BeanWrapperImpl
的结构比较
4.1 构造方法
/**
* Create a new instance.
* @param target the target object to bind onto (or {@code null}
* if the binder is just used to convert a plain parameter value)
* @param objectName the name of the target object
* @see #DEFAULT_OBJECT_NAME
*/
public ExtendedServletRequestDataBinder(@Nullable Object target, String objectName) {
super(target, objectName);
}
父类ServletRequestDataBinder
的构造方法
/**
* Create a new ServletRequestDataBinder instance.
* @param target the target object to bind onto (or {@code null}
* if the binder is just used to convert a plain parameter value)
* @param objectName the name of the target object
*/
public ServletRequestDataBinder(@Nullable Object target, String objectName) {
super(target, objectName);
}
父类WebDataBinder
的构造方法
/**
* Create a new WebDataBinder instance.
* @param target the target object to bind onto (or {@code null}
* if the binder is just used to convert a plain parameter value)
* @param objectName the name of the target object
*/
public WebDataBinder(@Nullable Object target, String objectName) {
super(target, objectName);
}
父类DataBinder
的构造方法
//要绑定的对象
@Nullable
private final Object target;
//要绑定参数的参数名
private final String objectName;
/**
* Create a new DataBinder instance.
* @param target the target object to bind onto (or {@code null}
* if the binder is just used to convert a plain parameter value)
* @param objectName the name of the target object
*/
public DataBinder(@Nullable Object target, String objectName) {
//从Optional容器中取出原始对象
this.target = ObjectUtils.unwrapOptional(target);
this.objectName = objectName;
}
构造方法额外做什么事情,只是简单的将两个参数值保存到字段中
4.2 DataBinder
的字段
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
/** Default object name used for binding: "target". */
public static final String DEFAULT_OBJECT_NAME = "target";
/** Default limit for array and collection growing: 256. */
public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;
//要绑定的对象
@Nullable
private final Object target;
//要绑定参数的参数名
private final String objectName;
//绑定结果
@Nullable
private AbstractPropertyBindingResult bindingResult;
@Nullable
private SimpleTypeConverter typeConverter;
private boolean ignoreUnknownFields = true;
private boolean ignoreInvalidFields = false;
//是否处理嵌套属性
private boolean autoGrowNestedPaths = true;
private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
@Nullable
private String[] allowedFields;
@Nullable
private String[] disallowedFields;
@Nullable
private String[] requiredFields;
//统一转换服务
@Nullable
private ConversionService conversionService;
@Nullable
private MessageCodesResolver messageCodesResolver;
private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
//校验器,默认是空的,需要用户自己配置
private final List<Validator> validators = new ArrayList<>();
}
上面字段的
setter
或getter
方法统一放在这个章节下面
4.2.1 setAutoGrowNestedPaths(boolean autoGrowNestedPaths)
方法,设置是否处理嵌套属性
public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call setAutoGrowNestedPaths before other configuration methods");
this.autoGrowNestedPaths = autoGrowNestedPaths;
}
4.2.2 getObjectName()
方法,获取要绑定参数的参数名
public String getObjectName() {
return this.objectName;
}
4.2.3 getTarget()
方法,获取要绑定的对象
@Nullable
public Object getTarget() {
return this.target;
}
4.2.4 getValidators()
方法, 获取配置的所有的校验器
public List<Validator> getValidators() {
return Collections.unmodifiableList(this.validators);
}
4.2.5 getBindingResult()
方法,获取BindingResult
绑定结果
public BindingResult getBindingResult() {
//获取BindingResult
return getInternalBindingResult();
}
获取BindingResult
/**
* Return the internal BindingResult held by this DataBinder,
* as an AbstractPropertyBindingResult.
*/
protected AbstractPropertyBindingResult getInternalBindingResult() {
//没有就创建一个,有就直接返回
if (this.bindingResult == null) {
//初始化bean属性寄存器,见4.4
initBeanPropertyAccess();
}
return this.bindingResult;
}
4.3 validate(Object... validationHints)
方法,校验数据
/**
* Invoke the specified Validators, if any, with the given validation hints.
* <p>Note: Validation hints may get ignored by the actual target Validator.
* @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
* @since 3.1
* @see #setValidator(Validator)
* @see SmartValidator#validate(Object, Errors, Object...)
*/
public void validate(Object... validationHints) {
//得到要绑定的对象,见4.2.3
Object target = getTarget();
Assert.state(target != null, "No target to validate");
//获取绑定结果
BindingResult bindingResult = getBindingResult();
// Call each validator with the same binding result
/**
* 遍历所有的校验器,校验数据
* getValidators()方法,获取配置的所有的校验器,见4.2.4
*/
for (Validator validator : getValidators()) {
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator) validator).validate(target, bindingResult, validationHints);
}
else if (validator != null) {
validator.validate(target, bindingResult);
}
}
}
4.4 initBeanPropertyAccess()
方法,初始化bean属性寄存器
/**
* Initialize standard JavaBean property access for this DataBinder.
* <p>This is the default; an explicit call just leads to eager initialization.
* @see #initDirectFieldAccess()
* @see #createBeanPropertyBindingResult()
*/
public void initBeanPropertyAccess() {
Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
//创建一个BindingResult
this.bindingResult = createBeanPropertyBindingResult();
}
创建一个BindingResult
/**
* Create the {@link AbstractPropertyBindingResult} instance using standard
* JavaBean property access.
* @since 4.2.1
*/
protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
//创建一个BeanPropertyBindingResult
BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(),
getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
//设置统一转换服务
if (this.conversionService != null) {
result.initConversion(this.conversionService);
}
//设置消息码解析器
if (this.messageCodesResolver != null) {
result.setMessageCodesResolver(this.messageCodesResolver);
}
return result;
}
BeanPropertyBindingResult
:通过setter
方法完成属性绑定
4.5 bind(ServletRequest request)
方法,数据绑定器的核心方法之一
该方法会将
request
请求参数绑定到javaBean
对象中
/**
* Bind the parameters of the given request to this binder's target,
* also binding multipart files in case of a multipart request.
* <p>This call can create field errors, representing basic binding
* errors like a required field (code "required"), or type mismatch
* between value and bean property (code "typeMismatch").
* <p>Multipart files are bound via their parameter name, just like normal
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
* invoking a "setUploadedFile" setter method.
* <p>The type of the target property for a multipart file can be MultipartFile,
* byte[], or String. The latter two receive the contents of the uploaded file;
* all metadata like original file name, content type, etc are lost in those cases.
* @param request the request with parameters to bind (can be multipart)
* @see org.springframework.web.multipart.MultipartHttpServletRequest
* @see org.springframework.web.multipart.MultipartFile
* @see #bind(org.springframework.beans.PropertyValues)
*/
public void bind(ServletRequest request) {
/**
* ServletRequestParameterPropertyValues中封装了所有请求参数,无前缀
*/
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
//得到文件上传请求对象
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
//文件上传请求的MultipartFile添加到mpvs中,见4.5.1
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
//将模板变量映射添加到mpvs中,见4.5.2
addBindValues(mpvs, request);
//绑定
doBind(mpvs);
}
4.5.1 将文件上传映射添加到MutablePropertyValues
中
/**
* Bind all multipart files contained in the given request, if any
* (in case of a multipart request). To be called by subclasses.
* <p>Multipart files will only be added to the property values if they
* are not empty or if we're configured to bind empty multipart files too.
* @param multipartFiles a Map of field name String to MultipartFile object
* @param mpvs the property values to be bound (can be modified)
* @see org.springframework.web.multipart.MultipartFile
* @see #setBindEmptyMultipartFiles
*/
protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
//遍历,保存
multipartFiles.forEach((key, values) -> {
if (values.size() == 1) {
MultipartFile value = values.get(0);
if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
mpvs.add(key, value);
}
}
else {
mpvs.add(key, values);
}
});
}
4.5.2 将模板变量映射添加到MutablePropertyValues
中
/**
* Merge URI variables into the property values to use for data binding.
*/
@Override
@SuppressWarnings("unchecked")
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
/**
* HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE=
* org.springframework.web.servlet.HandlerMapping.uriTemplateVariables
* 所有的模板变量映射都保存在这个请求域中
*/
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
if (uriVars != null) {
//遍历保存不重复的
uriVars.forEach((name, value) -> {
if (mpvs.contains(name)) {
if (logger.isWarnEnabled()) {
logger.warn("Skipping URI variable '" + name +
"' because request contains bind value with same name.");
}
}
else {
mpvs.addPropertyValue(name, value);
}
});
}
}
4.5.3 绑定到javaBean
对象中
/**
* This implementation performs a field default and marker check
* before delegating to the superclass binding process.
* @see #checkFieldDefaults
* @see #checkFieldMarkers
*/
@Override
protected void doBind(MutablePropertyValues mpvs) {
//检查所有默认字段,去掉那些不可写的字段,见4.5.3.1
checkFieldDefaults(mpvs);
//检查所有字段标记,去掉那些不可写的字段,见4.5.3.2
checkFieldMarkers(mpvs);
//父类再进一步处理,完成属性填充
super.doBind(mpvs);
}
父类doBind(mpvs)
方法再进一步处理,完成属性填充
/**
* Actual implementation of the binding process, working with the
* passed-in MutablePropertyValues instance.
* @param mpvs the property values to bind,
* as MutablePropertyValues instance
* @see #checkAllowedFields
* @see #checkRequiredFields
* @see #applyPropertyValues
*/
protected void doBind(MutablePropertyValues mpvs) {
//检查所有字段,移除掉用户指定的不允许绑定的字段,见4.5.3.3
checkAllowedFields(mpvs);
//检查用户指定的必须绑定的字段有没有对应值,见4.5.3.4
checkRequiredFields(mpvs);
//进行对象属性填充
applyPropertyValues(mpvs);
}
4.5.3.1 检查所有默认字段,去掉那些不可写的字段
/**
* Check the given property values for field defaults,
* i.e. for fields that start with the field default prefix.
* <p>The existence of a field defaults indicates that the specified
* value should be used if the field is otherwise not present.
* @param mpvs the property values to be bound (can be modified)
* @see #getFieldDefaultPrefix
*/
protected void checkFieldDefaults(MutablePropertyValues mpvs) {
//fieldDefaultPrefix = "!"
String fieldDefaultPrefix = getFieldDefaultPrefix();
if (fieldDefaultPrefix != null) {
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
//以"!"开头,就截取下来,然后判断对应字段是否可写
if (pv.getName().startsWith(fieldDefaultPrefix)) {
String field = pv.getName().substring(fieldDefaultPrefix.length());
/**
* getPropertyAccessor()方法, 获取需要绑定对象的属性寄存器
* isWritableProperty(field)方法,判断field对应的字段有没有setter方法
*/
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
//保存
mpvs.add(field, pv.getValue());
}
mpvs.removePropertyValue(pv);
}
}
}
}
对于以
!
开头的属性,截取掉!
得到field
,然后判field
对应的字段是否可写,可写,就再次保存field
映射关系到MutablePropertyValues
中,并移除掉这个属性,否则直接移除掉这个属性
4.5.3.2 检查所有字段标记,去掉那些不可写的字段
/**
* Check the given property values for field markers,
* i.e. for fields that start with the field marker prefix.
* <p>The existence of a field marker indicates that the specified
* field existed in the form. If the property values do not contain
* a corresponding field value, the field will be considered as empty
* and will be reset appropriately.
* @param mpvs the property values to be bound (can be modified)
* @see #getFieldMarkerPrefix
* @see #getEmptyValue(String, Class)
*/
protected void checkFieldMarkers(MutablePropertyValues mpvs) {
//fieldMarkerPrefix = "_"
String fieldMarkerPrefix = getFieldMarkerPrefix();
if (fieldMarkerPrefix != null) {
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
//以"_"开头,就截取下来,然后判断对应字段是否可写
if (pv.getName().startsWith(fieldMarkerPrefix)) {
String field = pv.getName().substring(fieldMarkerPrefix.length());
/**
* getPropertyAccessor()方法, 获取需要绑定对象的属性寄存器
* isWritableProperty(field)方法,判断field对应的字段有没有setter方法
*/
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
Class<?> fieldType = getPropertyAccessor().getPropertyType(field);
mpvs.add(field, getEmptyValue(field, fieldType));
}
mpvs.removePropertyValue(pv);
}
}
}
}
对于以
_
开头的属性,截取掉_
得到field
,然后判field
对应的字段是否可写,可写,就再次保存field
映射关系到MutablePropertyValues
中,并移除掉这个属性,否则直接移除掉这个属性
4.5.3.3 检查所有字段,移除掉用户指定的不允许绑定的字段
/**
* Check the given property values against the allowed fields,
* removing values for fields that are not allowed.
* @param mpvs the property values to be bound (can be modified)
* @see #getAllowedFields
* @see #isAllowed(String)
*/
protected void checkAllowedFields(MutablePropertyValues mpvs) {
PropertyValue[] pvs = mpvs.getPropertyValues();
for (PropertyValue pv : pvs) {
String field = PropertyAccessorUtils.canonicalPropertyName(pv.getName());
//移除所有不允许绑定的字段的映射
if (!isAllowed(field)) {
mpvs.removePropertyValue(pv);
getBindingResult().recordSuppressedField(field);
if (logger.isDebugEnabled()) {
logger.debug("Field [" + field + "] has been removed from PropertyValues " +
"and will not be bound, because it has not been found in the list of allowed fields");
}
}
}
}
/**
* Return if the given field is allowed for binding.
* Invoked for each passed-in property value.
* <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches,
* as well as direct equality, in the specified lists of allowed fields and
* disallowed fields. A field matching a disallowed pattern will not be accepted
* even if it also happens to match a pattern in the allowed list.
* <p>Can be overridden in subclasses.
* @param field the field to check
* @return if the field is allowed
* @see #setAllowedFields
* @see #setDisallowedFields
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
*/
protected boolean isAllowed(String field) {
/**
* 用户可以在数据绑定器中指定
* 允许绑定的字段allowedFields
* 不允许绑定的字段disallowedFields
*/
String[] allowed = getAllowedFields();
String[] disallowed = getDisallowedFields();
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
}
该方法移除掉用户指定的不允许绑定的字段映射
4.5.3.4 检查用户指定的必须绑定的字段有没有对应值
/**
* Check the given property values against the required fields,
* generating missing field errors where appropriate.
* @param mpvs the property values to be bound (can be modified)
* @see #getRequiredFields
* @see #getBindingErrorProcessor
* @see BindingErrorProcessor#processMissingFieldError
*/
protected void checkRequiredFields(MutablePropertyValues mpvs) {
/**
* requiredFields字段保存所有必须绑定的字段名
* 用户可通过指定这些字段值来定制要绑定的字段
*/
String[] requiredFields = getRequiredFields();
if (!ObjectUtils.isEmpty(requiredFields)) {
Map<String, PropertyValue> propertyValues = new HashMap<>();
PropertyValue[] pvs = mpvs.getPropertyValues();
for (PropertyValue pv : pvs) {
//规范名
String canonicalName = PropertyAccessorUtils.canonicalPropertyName(pv.getName());
propertyValues.put(canonicalName, pv);
}
//遍历所有必须填充字段
for (String field : requiredFields) {
PropertyValue pv = propertyValues.get(field);
boolean empty = (pv == null || pv.getValue() == null);
if (!empty) {
//String
if (pv.getValue() instanceof String) {
empty = !StringUtils.hasText((String) pv.getValue());
}
//String[]
else if (pv.getValue() instanceof String[]) {
String[] values = (String[]) pv.getValue();
empty = (values.length == 0 || !StringUtils.hasText(values[0]));
}
}
//生成一个绑定错误并设置到绑定结果中
if (empty) {
// Use bind error processor to create FieldError.
getBindingErrorProcessor().processMissingFieldError(field, getInternalBindingResult());
// Remove property from property values to bind:
// It has already caused a field error with a rejected value.
if (pv != null) {
mpvs.removePropertyValue(pv);
propertyValues.remove(field);
}
}
}
}
}
requiredFields
字段保存着所有必须绑定的字段名该方法遍历这些字段名,通过字段名从映射中取值,取不到值就生成一个绑定错误并设置到绑定结果中
4.5.3.5 进行对象属性填充
/**
* Apply given property values to the target object.
* <p>Default implementation applies all of the supplied property
* values as bean property values. By default, unknown fields will
* be ignored.
* @param mpvs the property values to be bound (can be modified)
* @see #getTarget
* @see #getPropertyAccessor
* @see #isIgnoreUnknownFields
* @see #getBindingErrorProcessor
* @see BindingErrorProcessor#processPropertyAccessException
*/
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
/**
* getPropertyAccessor()方法, 获取javaBean对象的属性寄存器,即BeanWrapperImpl
* setPropertyValues()方法,我们应该很熟悉,spring中的属性填充也是该方法完成的
*
*/
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
4.6 getPropertyAccessor()
方法,获取需要绑定对象的属性寄存器
/**
* Return the underlying PropertyAccessor of this binder's BindingResult.
*/
protected ConfigurablePropertyAccessor getPropertyAccessor() {
return getInternalBindingResult().getPropertyAccessor();
}
/**
* Return the internal BindingResult held by this DataBinder,
* as an AbstractPropertyBindingResult.
*/
protected AbstractPropertyBindingResult getInternalBindingResult() {
if (this.bindingResult == null) {
//初始化bean属性寄存器,见4.4
initBeanPropertyAccess();
}
return this.bindingResult;
}
从
4.4
中,我们知道,初始化的绑定结果类型为BeanPropertyBindingResult
进入
BeanPropertyBindingResult
类的getPropertyAccessor()
方法
/**
* Returns the {@link BeanWrapper} that this instance uses.
* Creates a new one if none existed before.
* @see #createBeanWrapper()
*/
@Override
public final ConfigurablePropertyAccessor getPropertyAccessor() {
if (this.beanWrapper == null) {
//创建一个BeanWrapperImpl对象
this.beanWrapper = createBeanWrapper();
this.beanWrapper.setExtractOldValueForEditor(true);
this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
}
return this.beanWrapper;
}
/**
* Create a new {@link BeanWrapper} for the underlying target object.
* @see #getTarget()
*/
protected BeanWrapper createBeanWrapper() {
if (this.target == null) {
throw new IllegalStateException("Cannot access properties on null bean instance '" + getObjectName() + "'");
}
//实际上就是new BeanWrapperImpl(target)
return PropertyAccessorFactory.forBeanPropertyAccess(this.target);
}
该方法本质上就是使用
new BeanWrapperImpl(需要绑定的对象)
,创建一个属性寄存器
5 BeanPropertyBindingResult
用于注册和评估
javaBean
对象上的绑定错误,执行标准的javaBean
属性访问
先来看它的类图
5.1 它们拥有的字段
AbstractBindingResult
public abstract class AbstractBindingResult extends AbstractErrors implements BindingResult, Serializable {
//要绑定参数的参数名
private final String objectName;
//消息码解析器
private MessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
//产生的错误对象都缓存在此处,
private final List<ObjectError> errors = new ArrayList<>();
//缓存,所有字段类型
private final Map<String, Class<?>> fieldTypes = new HashMap<>();
//缓存,所有字段属性
private final Map<String, Object> fieldValues = new HashMap<>();
private final Set<String> suppressedFields = new HashSet<>();
}
AbstractPropertyBindingResult
public abstract class AbstractPropertyBindingResult extends AbstractBindingResult {
//统一转换服务
@Nullable
private transient ConversionService conversionService;
}
BeanPropertyBindingResult
public class BeanPropertyBindingResult extends AbstractPropertyBindingResult implements Serializable {
//要绑定的参数对象
@Nullable
private final Object target;
private final boolean autoGrowNestedPaths;
private final int autoGrowCollectionLimit;
@Nullable
private transient BeanWrapper beanWrapper;
}
5.1.1 hasErrors()
方法,判断数据绑定过程中是否产生错误
@Override
public boolean hasErrors() {
return !this.errors.isEmpty();
}
判断数据绑定过程中是否产生错误很简单,数据绑定器会把绑定错误产生的对象保存到
errors
字段中,用户只需要判断errors
字段中是否为null
即可知道是否产生了错误
5.2 构造方法
/**
* Creates a new instance of the {@link BeanPropertyBindingResult} class.
* @param target the target bean to bind onto
* @param objectName the name of the target object
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
* @param autoGrowCollectionLimit the limit for array and collection auto-growing
*/
public BeanPropertyBindingResult(@Nullable Object target, String objectName,
boolean autoGrowNestedPaths, int autoGrowCollectionLimit) {
super(objectName);
//要进行绑定的对象
this.target = target;
//是否允许处理嵌套属性
this.autoGrowNestedPaths = autoGrowNestedPaths;
//嵌套限制
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
父类AbstractPropertyBindingResult
构造方法
/**
* Create a new AbstractPropertyBindingResult instance.
* @param objectName the name of the target object
* @see DefaultMessageCodesResolver
*/
protected AbstractPropertyBindingResult(String objectName) {
super(objectName);
}
父类AbstractPropertyBindingResult
构造方法
/**
* Create a new AbstractBindingResult instance.
* @param objectName the name of the target object
* @see DefaultMessageCodesResolver
*/
protected AbstractBindingResult(String objectName) {
//最终保存参数名
this.objectName = objectName;
}
5.3 getModel()
方法,获取绑定结果的模型数据
/**
* Return a model Map for the obtained state, exposing an Errors
* instance as '{@link #MODEL_KEY_PREFIX MODEL_KEY_PREFIX} + objectName'
* and the object itself.
* <p>Note that the Map is constructed every time you're calling this method.
* Adding things to the map and then re-calling this method will not work.
* <p>The attributes in the model Map returned by this method are usually
* included in the ModelAndView for a form view that uses Spring's bind tag,
* which needs access to the Errors instance.
* @see #getObjectName
* @see #MODEL_KEY_PREFIX
*/
@Override
public Map<String, Object> getModel() {
Map<String, Object> model = new LinkedHashMap<>(2);
// Mapping from name to target object.
//绑定的参数名->要绑定的对象
model.put(getObjectName(), getTarget());
// Errors instance, even if no errors.
/**
* MODEL_KEY_PREFIX=org.springframework.validation.BindingResult.
* 前缀+绑定的参数名->绑定结果
*/
model.put(MODEL_KEY_PREFIX + getObjectName(), this);
return model;
}
得到两个映射关系
- 绑定的参数名->要绑定的对象
- 前缀+绑定的参数名->绑定结果
6 ServletRequestParameterPropertyValues
先看类图
MutablePropertyValues
我们应该很熟悉了,它内部有一个PropertyValue
的list
集合字段,他所有的增删改查和迭代操作都是基于这个list
集合字段。
下面是该类的源码
public class ServletRequestParameterPropertyValues extends MutablePropertyValues {
/** Default prefix separator. */
public static final String DEFAULT_PREFIX_SEPARATOR = "_";
/**
* Create new ServletRequestPropertyValues using no prefix
* (and hence, no prefix separator).
* @param request the HTTP request
*/
public ServletRequestParameterPropertyValues(ServletRequest request) {
this(request, null, null);
}
/**
* Create new ServletRequestPropertyValues using the given prefix and
* the default prefix separator (the underscore character "_").
* @param request the HTTP request
* @param prefix the prefix for parameters (the full prefix will
* consist of this plus the separator)
* @see #DEFAULT_PREFIX_SEPARATOR
*/
public ServletRequestParameterPropertyValues(ServletRequest request, @Nullable String prefix) {
this(request, prefix, DEFAULT_PREFIX_SEPARATOR);
}
/**
* Create new ServletRequestPropertyValues supplying both prefix and
* prefix separator.
* @param request the HTTP request
* @param prefix the prefix for parameters (the full prefix will
* consist of this plus the separator)
* @param prefixSeparator separator delimiting prefix (e.g. "spring")
* and the rest of the parameter name ("param1", "param2")
*/
public ServletRequestParameterPropertyValues(
ServletRequest request, @Nullable String prefix, @Nullable String prefixSeparator) {
//父类构造方法中将map转化为List<PropertyValue>
super(WebUtils.getParametersStartingWith(
request, (prefix != null ? prefix + prefixSeparator : null)));
}
}
该类用来封装
request
请求的请求参数,通过传入request
对象和指定前缀,就能得到所有指定前缀的请求参数
WebUtils.getParametersStartingWith()
方法,获取所有指定前缀的请求参数
/**
* Return a map containing all parameters with the given prefix.
* Maps single values to String and multiple values to String array.
* <p>For example, with a prefix of "spring_", "spring_param1" and
* "spring_param2" result in a Map with "param1" and "param2" as keys.
* @param request the HTTP request in which to look for parameters
* @param prefix the beginning of parameter names
* (if this is null or the empty string, all parameters will match)
* @return map containing request parameters <b>without the prefix</b>,
* containing either a String or a String array as values
* @see javax.servlet.ServletRequest#getParameterNames
* @see javax.servlet.ServletRequest#getParameterValues
* @see javax.servlet.ServletRequest#getParameterMap
*/
public static Map<String, Object> getParametersStartingWith(ServletRequest request, @Nullable String prefix) {
Assert.notNull(request, "Request must not be null");
//获取所有请求参数名
Enumeration<String> paramNames = request.getParameterNames();
Map<String, Object> params = new TreeMap<>();
if (prefix == null) {
prefix = "";
}
//遍历找到指定前缀的请求参数
while (paramNames != null && paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
if (prefix.isEmpty() || paramName.startsWith(prefix)) {
String unprefixed = paramName.substring(prefix.length());
String[] values = request.getParameterValues(paramName);
if (values == null || values.length == 0) {
// Do nothing, no values found at all.
}
else if (values.length > 1) {
params.put(unprefixed, values);
}
else {
params.put(unprefixed, values[0]);
}
}
}
return params;
}