2023-09-12  阅读(1)
原文作者:一直不懂 原文地址: https://blog.csdn.net/shenchaohao12321/article/details/80356890

DataBinder实现了TypeConverter和PropertyEditorRegistry接口提供了类型转换功能,并且可以对目标对象字段做Validation

202309122023031601.png

DataBinder有个重要的成员变量bindingResult是AbstractPropertyBindingResult类,我们先分析他的用处。

202309122023038972.png

Errors接口定义了存储与展示关于数据绑定和validation到指定对象的错误信息,AbstractErrors是一个抽象实现。对于错误信息存储的四个方法:

    @Override
    public void reject(String errorCode) {
       reject(errorCode, null, null);
    }
    @Override
    public void reject(String errorCode, String defaultMessage) {
       reject(errorCode, null, defaultMessage);
    }
    @Override
    public void rejectValue(@Nullable String field, String errorCode) {
       rejectValue(field, errorCode, null, null);
    }
    @Override
    public void rejectValue(@Nullable String field, String errorCode, String defaultMessage) {
       rejectValue(field, errorCode, null, defaultMessage);
    }

reject()和rejectValue()最终的实现方法被定义在了子类AbstractBindingResult,reject()方法用于为指定对象注册一个全局的错误信息,rejectValue()方法用于为一个对象的指定字段注册一个错误消息。

同样获取错误信息的方法也是给出了基本实现,最终的实现还是定义在了子类中。

    @Override
    @Nullable
    public ObjectError getGlobalError() {
       List<ObjectError> globalErrors = getGlobalErrors();
       return (!globalErrors.isEmpty() ? globalErrors.get(0) : null);
    }
    @Override
    public boolean hasFieldErrors() {
       return (getFieldErrorCount() > 0);
    }
    @Override
    public int getFieldErrorCount() {
       return getFieldErrors().size();
    }
    @Override
    @Nullable
    public FieldError getFieldError() {
       List<FieldError> fieldErrors = getFieldErrors();
       return (!fieldErrors.isEmpty() ? fieldErrors.get(0) : null);
    }
    @Override
    public boolean hasFieldErrors(String field) {
       return (getFieldErrorCount(field) > 0);
    }
    @Override
    public int getFieldErrorCount(String field) {
       return getFieldErrors(field).size();
    }
    @Override
    public List<FieldError> getFieldErrors(String field) {
       List<FieldError> fieldErrors = getFieldErrors();
       List<FieldError> result = new LinkedList<>();
       String fixedField = fixedField(field);
       for (FieldError error : fieldErrors) {
          if (isMatchingFieldError(fixedField, error)) {
             result.add(error);
          }
       }
       return Collections.unmodifiableList(result);
    }
    @Override
    @Nullable
    public FieldError getFieldError(String field) {
       List<FieldError> fieldErrors = getFieldErrors(field);
       return (!fieldErrors.isEmpty() ? fieldErrors.get(0) : null);
    }
    @Override
    @Nullable
    public Class<?> getFieldType(String field) {
       Object value = getFieldValue(field);
       return (value != null ? value.getClass() : null);
    }

BindingResult接口扩展了Errors接口,额外定义了一些与数据绑定结果相关的方法,下面就看一看BindingResult与AbstractErrors共同的子类AbstractBindingResult。

主要成员变量:

    private final String objectName;//为绑定对象起个名字,会作用于错误码和getModel()
    private MessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();//用于处理错误码,看下面解释
    private final List<ObjectError> errors = new LinkedList<>();//存储数据绑定校验出现的错误
    private final Map<String, Class<?>> fieldTypes = new HashMap<>(0);//字段名与字段类型
    private final Map<String, Object> fieldValues = new HashMap<>(0);//字段名与字段值
    private final Set<String> suppressedFields = new HashSet<>();//存储数据绑定不被允许的字段

reject()方法内部会将ObjectError或FieldError对象添加到成员变量errors中,这两个对象需要一个String[] codes,messageCodesResolver就是将reject方法参数errorCode转换为codes的作用。看一下他的文档描述。

202309122023042903.png

下面是注册错误消息的实现:

    @Override
    public void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage) {
       addError(new ObjectError(getObjectName(), resolveMessageCodes(errorCode), errorArgs, defaultMessage));
    }
    @Override
    public void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs,@Nullable String defaultMessage) {
       if ("".equals(getNestedPath()) && !StringUtils.hasLength(field)) {
          // We're at the top of the nested object hierarchy,
          // so the present level is not a field but rather the top object.
          // The best we can do is register a global error here...
          reject(errorCode, errorArgs, defaultMessage);
          return;
       }
       String fixedField = fixedField(field);
       Object newVal = getActualFieldValue(fixedField);
       FieldError fe = new FieldError(getObjectName(), fixedField, newVal, false, resolveMessageCodes(errorCode, field), errorArgs, defaultMessage);
       addError(fe);
    }
    @Override
    public void addError(ObjectError error) {
       this.errors.add(error);
    }

对于全局错误使用ObjectError,对于字段错误使用FieldError多了字段名字与字段值。

获取错误信息:

    @Override
    public List<ObjectError> getGlobalErrors() {
       List<ObjectError> result = new LinkedList<>();
       for (ObjectError objectError : this.errors) {
          if (!(objectError instanceof FieldError)) {
             result.add(objectError);
          }
       }
       return Collections.unmodifiableList(result);
    }
    @Override
    @Nullable
    public ObjectError getGlobalError() {
       for (ObjectError objectError : this.errors) {
          if (!(objectError instanceof FieldError)) {
             return objectError;
          }
       }
       return null;
    }
    @Override
    public List<FieldError> getFieldErrors() {
       List<FieldError> result = new LinkedList<>();
       for (ObjectError objectError : this.errors) {
          if (objectError instanceof FieldError) {
             result.add((FieldError) objectError);
          }
       }
       return Collections.unmodifiableList(result);
    }
    @Override
    @Nullable
    public FieldError getFieldError() {
       for (ObjectError objectError : this.errors) {
          if (objectError instanceof FieldError) {
             return (FieldError) objectError;
          }
       }
       return null;
    }
    @Override
    public List<FieldError> getFieldErrors(String field) {
       List<FieldError> result = new LinkedList<>();
       String fixedField = fixedField(field);
       for (ObjectError objectError : this.errors) {
          if (objectError instanceof FieldError && isMatchingFieldError(fixedField, (FieldError) objectError)) {
             result.add((FieldError) objectError);
          }
       }
       return Collections.unmodifiableList(result);
    }
    @Override
    @Nullable
    public FieldError getFieldError(String field) {
       String fixedField = fixedField(field);
       for (ObjectError objectError : this.errors) {
          if (objectError instanceof FieldError) {
             FieldError fieldError = (FieldError) objectError;
             if (isMatchingFieldError(fixedField, fieldError)) {
                return fieldError;
             }
          }
       }
       return null;
    }

isMatchingFieldError()方法支持*号匹配。

    protected boolean isMatchingFieldError(String field, FieldError fieldError) {
       if (field.equals(fieldError.getField())) {
          return true;
       }
       // Optimization: use charAt and regionMatches instead of endsWith and startsWith (SPR-11304)
       int endIndex = field.length() - 1;
       return (endIndex >= 0 && field.charAt(endIndex) == '*' &&
             (endIndex == 0 || field.regionMatches(0, fieldError.getField(), 0, endIndex)));
    }
    @Override
    @Nullable
    public Object getFieldValue(String field) {
       FieldError fieldError = getFieldError(field);
       // Use rejected value in case of error, current field value otherwise.
       if (fieldError != null) {
          Object value = fieldError.getRejectedValue();
          // Do not apply formatting on binding failures like type mismatches.
          return (fieldError.isBindingFailure() ? value : formatFieldValue(field, value));
       }
       else if (getTarget() != null) {
          Object value = getActualFieldValue(fixedField(field));
          return formatFieldValue(field, value);
       }
       else {
          return this.fieldValues.get(field);
       }
    }
    @Override
    @Nullable
    public Class<?> getFieldType(@Nullable String field) {
       if (getTarget() != null) {
          Object value = getActualFieldValue(fixedField(field));
          if (value != null) {
             return value.getClass();
          }
       }
       return this.fieldTypes.get(field);
    }
    @Override
    @Nullable
    public Object getRawFieldValue(String field) {
       return (getTarget() != null ? getActualFieldValue(fixedField(field)) : null);
    }

上面方法中会用到子类实现方法getActualFieldValue(),formatFieldValue()。看一下在AbstractPropertyBindingResult中的实现:

    @Override
    @Nullable
    public Class<?> getFieldType(@Nullable String field) {
       return (getTarget() != null ? getPropertyAccessor().getPropertyType(fixedField(field)) :
             super.getFieldType(field));
    }
    @Override
    @Nullable
    protected Object getActualFieldValue(String field) {
       return getPropertyAccessor().getPropertyValue(field);
    }
    
    @Override
    protected Object formatFieldValue(String field, @Nullable Object value) {
       String fixedField = fixedField(field);
       // Try custom editor...
       PropertyEditor customEditor = getCustomEditor(fixedField);
       if (customEditor != null) {
          customEditor.setValue(value);
          String textValue = customEditor.getAsText();
          // If the PropertyEditor returned null, there is no appropriate
          // text representation for this value: only use it if non-null.
          if (textValue != null) {
             return textValue;
          }
       }
       if (this.conversionService != null) {
          // Try custom converter...
          TypeDescriptor fieldDesc = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
          TypeDescriptor strDesc = TypeDescriptor.valueOf(String.class);
          if (fieldDesc != null && this.conversionService.canConvert(fieldDesc, strDesc)) {
             return this.conversionService.convert(value, fieldDesc, strDesc);
          }
       }
       return value;
    }

getPropertyAccessor()方法是一个抽象方法,由子类实现,看下在 BeanPropertyBindingResult中的实现:

    @Override
    public final ConfigurablePropertyAccessor getPropertyAccessor() {
       if (this.beanWrapper == null) {
          this.beanWrapper = createBeanWrapper();
          this.beanWrapper.setExtractOldValueForEditor(true);
          this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
          this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
       }
       return this.beanWrapper;
    }
    protected BeanWrapper createBeanWrapper() {
       if (this.target == null) {
          throw new IllegalStateException("Cannot access properties on null bean instance '" + getObjectName() + "'");
       }
       return PropertyAccessorFactory.forBeanPropertyAccess(this.target);
    }

BeanPropertyBindingResult,Errors和BindingResult接口的默认实现,用于注册和评估JavaBean对象上的绑定错误。执行标准JavaBean属性访问,也支持嵌套属性。 通常,应用程序代码将与Errors接口或BindingResult接口一起使用。DataBinder通过getBindingResult()方法返回其BindingResult。

    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;
       public BeanPropertyBindingResult(@Nullable Object target, String objectName) {
          this(target, objectName, true, Integer.MAX_VALUE);
       }
       public BeanPropertyBindingResult(@Nullable Object target, String objectName, boolean autoGrowNestedPaths, int autoGrowCollectionLimit) {
          super(objectName);
          this.target = target;
          this.autoGrowNestedPaths = autoGrowNestedPaths;
          this.autoGrowCollectionLimit = autoGrowCollectionLimit;
       }
       @Override
       @Nullable
       public final Object getTarget() {
          return this.target;
       }
       @Override
       public final ConfigurablePropertyAccessor getPropertyAccessor() {
          if (this.beanWrapper == null) {
             this.beanWrapper = createBeanWrapper();
             this.beanWrapper.setExtractOldValueForEditor(true);
             this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
             this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
          }
          return this.beanWrapper;
       }
       protected BeanWrapper createBeanWrapper() {
          if (this.target == null) {
             throw new IllegalStateException("Cannot access properties on null bean instance '" + getObjectName() + "'");
          }
          return PropertyAccessorFactory.forBeanPropertyAccess(this.target);
       }
    }

我们已经知道了BindingResult的体系机构了,下面正式说一下DataBinder了。

    @Nullable
    private final Object target;//需要数据绑定的对象
    private final String objectName;//给对象起得名字默认target
    @Nullable
    private AbstractPropertyBindingResult bindingResult;//数据绑定后的结果
    @Nullable
    private SimpleTypeConverter typeConverter;//当target!=null时不会用到
    private boolean ignoreUnknownFields = true;//忽略target不存在的属性,作用于PropertyAccessor的setPropertyValues()方法
    private boolean ignoreInvalidFields = false;//忽略target不能访问的属性
    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;//为getPropertyAccessor().setConversionService(conversionService);
    @Nullable
    private MessageCodesResolver messageCodesResolver;//同bindingResult的
    private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
    private final List<Validator> validators = new ArrayList<>();//自定义数据校验器

BindingErrorProcessor接口定义了两个方法,用于处理不能存在的属性和将PropertyAccessException转换成一个FieldError。

    public class DefaultBindingErrorProcessor implements BindingErrorProcessor {
       public static final String MISSING_FIELD_ERROR_CODE = "required";
       @Override
       public void processMissingFieldError(String missingField, BindingResult bindingResult) {
          // Create field error with code "required".
          String fixedField = bindingResult.getNestedPath() + missingField;
          String[] codes = bindingResult.resolveMessageCodes(MISSING_FIELD_ERROR_CODE, missingField);
          Object[] arguments = getArgumentsForBindError(bindingResult.getObjectName(), fixedField);
          FieldError error = new FieldError(bindingResult.getObjectName(), fixedField, "", true,
                codes, arguments, "Field '" + fixedField + "' is required");
          bindingResult.addError(error);
       }
       @Override
       public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
          // Create field error with the exceptions's code, e.g. "typeMismatch".
          String field = ex.getPropertyName();
          Assert.state(field != null, "No field in exception");
          String[] codes = bindingResult.resolveMessageCodes(ex.getErrorCode(), field);
          Object[] arguments = getArgumentsForBindError(bindingResult.getObjectName(), field);
          Object rejectedValue = ex.getValue();
          if (ObjectUtils.isArray(rejectedValue)) {
             rejectedValue = StringUtils.arrayToCommaDelimitedString(ObjectUtils.toObjectArray(rejectedValue));
          }
          FieldError error = new FieldError(bindingResult.getObjectName(), field, rejectedValue, true,
                codes, arguments, ex.getLocalizedMessage());
          error.wrap(ex);
          bindingResult.addError(error);
       }
       protected Object[] getArgumentsForBindError(String objectName, String field) {
          String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};
          return new Object[] {new DefaultMessageSourceResolvable(codes, field)};
       }
    }

DataBinder 实现了PropertyEditorRegistry接口需要实现接口的方法,采用了代理的方式,bindingResult是BeanPropertyBindingResult的实例,内部会持有一个BeanWrapperImpl,PropertyEditorRegistry接口的实现都是委托了BeanWrapperImpl。

    @Override
    public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
       getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor);
    }
    protected PropertyEditorRegistry getPropertyEditorRegistry() {
       if (getTarget() != null) {
          return getInternalBindingResult().getPropertyAccessor();
       }
       else {
          return getSimpleTypeConverter();
       }
    }
    protected AbstractPropertyBindingResult getInternalBindingResult() {
       if (this.bindingResult == null) {
          initBeanPropertyAccess();
       }
       return this.bindingResult;
    }
    public void initBeanPropertyAccess() {
       Assert.state(this.bindingResult == null,"DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
       this.bindingResult = createBeanPropertyBindingResult();
    }
    protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
       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;
    }

bind()是数据绑定对象的核心方法:将给定的属性值绑定到此绑定程序的目标。此调用可以创建字段错误,表示基本绑定错误,如必填字段(代码“required”),或值和bean属性之间的类型不匹配(代码“typeMismatch”)。请注意,给定的PropertyValues应该是一次性实例:为了提高效率,如果它实现了MutablePropertyValues接口,它将被修改为只包含允许的字段; 否则,将为此目的创建内部可变副本。 如果您希望原始实例在任何情况下保持不变,请传递PropertyValues的副本。

    public void bind(PropertyValues pvs) {
       MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues) ?
             (MutablePropertyValues) pvs : new MutablePropertyValues(pvs);
       doBind(mpvs);
    }
    protected void doBind(MutablePropertyValues mpvs) {
       checkAllowedFields(mpvs);
       checkRequiredFields(mpvs);
       applyPropertyValues(mpvs);
    }

checkAllowedFields()方法不被允许的字段将移除,加入到bindingResult的suppressedFields中,这样就不会对该字段赋值并且记录下来,allowed和disallowed分别是通过setAllowedFields()和setDisallowedFields()方法设置的,默认null。

    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");
             }
          }
       }
    }
    protected boolean isAllowed(String field) {
       String[] allowed = getAllowedFields();
       String[] disallowed = getDisallowedFields();
       return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
             (ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
    }

checkRequiredFields()方法检查必须的字段是否存在或者可以访问,不满足则加入resultBinding中一个errorCode为required的FieldError对象。

    protected void checkRequiredFields(MutablePropertyValues mpvs) {
       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) {
                if (pv.getValue() instanceof String) {
                   empty = !StringUtils.hasText((String) pv.getValue());
                }
                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);
                }
             }
          }
       }
    }

applyPropertyValues()方法使用resultBinding对象内的BeanWraperImpl对象完成属性的赋值操作,这个上篇讲过。

    protected void applyPropertyValues(MutablePropertyValues mpvs) {
       try {
          // Bind request parameters onto target object.
          getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
       }
       catch (PropertyBatchUpdateException ex) {
          // Use bind error processor to create FieldErrors.
          for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
             getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
          }
       }
    }

需要注意的是,在PropertyAccessor的setPropertyValues()方法实现中AbstractPropertyAccessor给出了一个模板方法的实现:

    public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
          throws BeansException {
       List<PropertyAccessException> propertyAccessExceptions = null;
       List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
             ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
       for (PropertyValue pv : propertyValues) {
          try {
             // This method may throw any BeansException, which won't be caught
             // here, if there is a critical failure such as no matching field.
             // We can attempt to deal only with less serious exceptions.
             setPropertyValue(pv);
          }
          catch (NotWritablePropertyException ex) {
             if (!ignoreUnknown) {
                throw ex;
             }
             // Otherwise, just ignore it and continue...
          }
          catch (NullValueInNestedPathException ex) {
             if (!ignoreInvalid) {
                throw ex;
             }
             // Otherwise, just ignore it and continue...
          }
          catch (PropertyAccessException ex) {
             if (propertyAccessExceptions == null) {
                propertyAccessExceptions = new LinkedList<>();
             }
             propertyAccessExceptions.add(ex);
          }
       }
    
       // If we encountered individual exceptions, throw the composite exception.
       if (propertyAccessExceptions != null) {
          PropertyAccessException[] paeArray =
                propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
          throw new PropertyBatchUpdateException(paeArray);
       }
    }

setPropertyValue()方法运行的过程中可能会抛出各种PropertyAccessException,每种具体PropertyAccessException子类都有一个errorCode。抛出的这些异常会集中放入PropertyBatchUpdateException中打包发出。这是DataBinder的applyPropertyValues方法内会捕获这个异常,使用BindingErrorProcessor处理这些异常,转换为FieldError对象存储。

对象完成数据绑定后可以调用getBindingResult()方法,查看数据绑定后的各种数据。

WebDataBinder 是一个特殊的DataBinder,用于从Web请求参数到JavaBean对象的数据绑定。 专为Web环境而设计,但不依赖于Servlet API; 作为更具体的DataBinder变体的基类,例如ServletRequestDataBinder。包括对字段标记的支持,这些标记解决了HTML复选框和选择选项的常见问题:检测到字段是表单的一部分,但由于它是空的,因此未生成请求参数。
字段标记允许检测该状态并相应地重置相应的bean属性。 对于不存在的参数,默认值可以指定除空后的字段的值。在doBind()方法中加入了两个检查方法用于处理参数带前缀“!”和“_”。

    @Override
    protected void doBind(MutablePropertyValues mpvs) {
       checkFieldDefaults(mpvs);//_
       checkFieldMarkers(mpvs);//!
       super.doBind(mpvs);
    }
    //检查给定属性的字段默认值,即对于以字段默认前缀开头的字段。
    //字段默认值的存在表示如果该字段不存在则应使用指定的默认值。
    protected void checkFieldDefaults(MutablePropertyValues mpvs) {
       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());
                if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
                   mpvs.add(field, pv.getValue());
                }
                mpvs.removePropertyValue(pv);
             }
          }
       }
    }
    //检查字段标记的给定属性值,即对于以字段标记前缀开头的字段。
    //字段标记的存在表明指定的字段存在于表单中。 如果属性值不包含相应的字段值,则该字段将被视为空,并将被适当地重置。
    protected void checkFieldMarkers(MutablePropertyValues mpvs) {
       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());
                if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
                   Class<?> fieldType = getPropertyAccessor().getPropertyType(field);
                   mpvs.add(field, getEmptyValue(field, fieldType));
                }
                mpvs.removePropertyValue(pv);
             }
          }
       }
    }
    @Nullable
    protected Object getEmptyValue(String field, @Nullable Class<?> fieldType) {
       return (fieldType != null ? getEmptyValue(fieldType) : null);
    }
    @Nullable
    public Object getEmptyValue(Class<?> fieldType) {
       try {
          if (boolean.class == fieldType || Boolean.class == fieldType) {
             // Special handling of boolean property.
             return Boolean.FALSE;
          }
          else if (fieldType.isArray()) {
             // Special handling of array property.
             return Array.newInstance(fieldType.getComponentType(), 0);
          }
          else if (Collection.class.isAssignableFrom(fieldType)) {
             return CollectionFactory.createCollection(fieldType, 0);
          }
          else if (Map.class.isAssignableFrom(fieldType)) {
             return CollectionFactory.createMap(fieldType, 0);
          }
       }
       catch (IllegalArgumentException ex) {
          if (logger.isDebugEnabled()) {
             logger.debug("Failed to create default value - falling back to null: " + ex.getMessage());
          }
       }
       // Default value: null.
       return null;
    }

ServletRequestDataBinder 特殊的DataBinder,用于执行从servlet请求参数到JavaBeans的数据绑定,包括对multipart文件的支持。请参阅DataBinder / WebDataBinder超类以获取自定义选项,其中包括指定允许/必需字段以及注册自定义属性编辑器。也可用于自定义Web控制器中的手动数据绑定:例如,在普通的Controller实现中或在MultiActionController处理程序方法中。 只需为每个绑定过程实例化一个ServletRequestDataBinder,并使用当前的ServletRequest作为参数调用bind()方法。bind()方法将参数ServletRequest转换成 MutablePropertyValues再由父类做数据绑定,用于将Http Request请求属性绑定到相应的对象上。

    //将给定请求的参数绑定到此绑定程序的目标,并在多部分请求的情况下绑定多部分文件。
    此调用可以创建字段错误,表示基本绑定错误,如必填字段(代码“required”),或值和bean属性之间的类型不匹配(代码“typeMismatch”)。
    //Multipart文件通过其参数名称绑定,就像普通的HTTP参数一样:即“uploadedFile”到“uploadedFile”bean属性,调用“setUploadedFile”setter方法。
    //Multipart文件的目标属性的类型可以是MultipartFile,byte[]或String。 后两者接收上传文件的内容; 在这些情况下,所有元数据(如原始文件名,内容类型等)都将丢失。
    public void bind(ServletRequest request) {
       MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
       MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
       if (multipartRequest != null) {
          bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
       }
       addBindValues(mpvs, request);
       doBind(mpvs);
    }

具体测试请看org.springframework.web.bind.ServletRequestDataBinderTests。

ExtendedServletRequestDataBinder 是ServletRequestDataBinder的子类,它将URI模板变量添加到用于数据绑定的值。

    public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
       public ExtendedServletRequestDataBinder(@Nullable Object target) {
          super(target);
       }
       public ExtendedServletRequestDataBinder(@Nullable Object target, String objectName) {
          super(target, objectName);
       }
       //将URI变量合并到属性值中以用于数据绑定。
       @Override
       @SuppressWarnings("unchecked")
       protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
          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);
                }
             });
          }
       }
    }

Spring提供了一系列工厂类来创建对应的WebDataBinder对象:

202309122023050864.png

顶级接口定义了创建一个WebDataBinder的方法。

    public interface WebDataBinderFactory {
       //为给定对象创建{@link WebDataBinder}。target可以为null如果为简单类型创建
       WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception;
    }
    //创建一个WebRequestDataBinder实例并使用WebBindingInitializer对其进行初始化。
    public class DefaultDataBinderFactory implements WebDataBinderFactory {
       @Nullable
       private final WebBindingInitializer initializer;
       public DefaultDataBinderFactory(@Nullable WebBindingInitializer initializer) {
          this.initializer = initializer;
       }
       //为给定的目标对象创建一个新的WebDataBinder,并通过WebBindingInitializer对其进行初始化
       @Override
       @SuppressWarnings("deprecation")
       public final WebDataBinder createBinder(
             NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
          WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
          if (this.initializer != null) {
             this.initializer.initBinder(dataBinder, webRequest);
          }
          initBinder(dataBinder, webRequest);
          return dataBinder;
       }
       //扩展点以创建WebDataBinder实例。默认情况下是WebRequestDataBinder
       protected WebDataBinder createBinderInstance(
             @Nullable Object target, String objectName, NativeWebRequest webRequest) throws Exception {
          return new WebRequestDataBinder(target, objectName);
       }
       //扩展点通过WebBindingInitializer在“全局”初始化之后进一步初始化创建的数据绑定器实例(例如,使用@InitBinder方法
       protected void initBinder(WebDataBinder dataBinder, NativeWebRequest webRequest)
             throws Exception {
       }
    }
    //通过@InitBinder方法向WebDataBinder添加初始化操作
    public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
       private final List<InvocableHandlerMethod> binderMethods;
       //InvocableHandlerMethod就是@InitBinder方法的一个简单封装
       public InitBinderDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods,
             @Nullable WebBindingInitializer initializer) {
          super(initializer);
          this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());
       }
       //使用@InitBinder方法初始化WebDataBinder。
       //如果@InitBinder注释指定了属性名称,则只有在名称包含目标对象名称时才会调用它。
       @Override
       public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
          for (InvocableHandlerMethod binderMethod : this.binderMethods) {
             if (isBinderMethodApplicable(binderMethod, dataBinder)) {
                Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
                if (returnValue != null) {
                   throw new IllegalStateException(
                         "@InitBinder methods must not return a value (should be void): " + binderMethod);
                }
             }
          }
       }
       //确定是否应使用给定的@InitBinder方法初始化给定的WebDataBinder实例。 默认情况下,我们检查注释值中的指定属性名称(如果有)。
       protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
          InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
          Assert.state(ann != null, "No InitBinder annotation");
          String[] names = ann.value();
          return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
       }
    }
    public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory {
       public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods,
             @Nullable WebBindingInitializer initializer) {
          super(binderMethods, initializer);
       }
       //返回ExtendedServletRequestDataBinder
       @Override
       protected ServletRequestDataBinder createBinderInstance(
             @Nullable Object target, String objectName, NativeWebRequest request) throws Exception  {
          return new ExtendedServletRequestDataBinder(target, objectName);
       }
    }

关于WebDataBinderFactory的使用参考《Spring MVC设计原理》


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文