springmvc--7--数据绑定器WebDataBinder

springmvc–数据绑定器WebDataBinder

文章目录

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方法

下面是ExtendedServletRequestDataBinderBeanWrapperImpl的结构比较

在这里插入图片描述

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<>();
}

上面字段的settergetter方法统一放在这个章节下面

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我们应该很熟悉了,它内部有一个PropertyValuelist集合字段,他所有的增删改查和迭代操作都是基于这个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;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值