当前位置:   article > 正文

SpringMVC源码解读 --- 处理器适配器 - 3 源码解读前置知识WebDataBinder的父子结构及源码分析_checkfieldmarkers()

checkfieldmarkers()

     要了解这部分需要知道@ModelAttribute的基本使用

1、整个类的功能说明

        1、这里我们首先说明一下整体功能

        我们理解下这个WebDataBinder是用在哪里、是做什么用的。通过前一章 我们有理解这个WebDataBinder是有WebDataBinderFactory去创建的。所以,我们直接看其WebDataBinderFactory的createBinder方法的调用:

               

可以看到在这些地方有调用,同时通过前面章节梳理HandlerMethodArgumentResolver、及HandlerMethodResultResolver接口我们可以知道,这里的Resolver、Processor为后缀的类都是归属与这种,这种就是在解析对应参数的时候,用这个WebDataBinder去对获取到的初始解析类型转换、一些其他的校验处理:

每个参数都创建一个对应的WebDataBinder,因为这里是在HandlerMethodArgumentResolver接口的resolveArgument调用的createBinder方法

  2.、Demo说明

@RequestMapping(value = "methodHandler",method = RequestMethod.GET)
public String  methodHandler(@ModelAttribute("age") Integer age,
                               @ModelAttribute("name") String name,@ModelAttribute("type") Short type)
{
    System.out.println(".....methodHandler.....");
    return "springMethod";
}
@ModelAttribute
public void modelAttributeMethod(@RequestParam("age") Integer age
        , @RequestParam("type") Short type, Model model)
{
    model.addAttribute("age",age);
    model.addAttribute("type",type);
    System.out.println(".......modelAttributeMethod.....");
}
@InitBinder
public void initBinder(WebDataBinder binder) {
    CustomDateEditor editor = new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),true);
    binder.registerCustomEditor(Date.class, editor);

    binder.setRequiredFields("name");
}

   这里我们通过一个demo来说明一下这个类的用法。这里主要有两个点。可以结合前面的成员变量看。

    1、model.addAttribute()、binder.setRequiredFields("name");

     可以看到这里这里通过@ModelAttribute注解、@RequestParam注解,再将其add到Model中。然后在通过@InitBinder注解,去设置RequiredFields(需要的Field)。,同时我们可以看到@RequestMapping(value = "methodHandler")是有三个参数的。然后我们再请求是时,少一个参数,不写"name" :http://localhost:9999/methodHandler?age=25&type=1

    

  目前这里由于有@ModelAttribute注解,所以用到是ModelAttributeMethodProcessor(HandlerMethodArgumentResolver),可以看到这里,在进行bindRequestParameters方法后(会进行前面设置的setRequiredFields的内容校验,不通过就会放到erros中),可以看到,这里,由于"name"设置的必须的,所以不通过。下面我们来看下具体的类

   2、DataBinder

        1、主要成员变量

public class DataBinder implements PropertyEditorRegistry, TypeConverter {

   @Nullable
   private final Object target;
   private final String objectName;
   @Nullable
   private AbstractPropertyBindingResult bindingResult;
   @Nullable
   private SimpleTypeConverter typeConverter;
     ............
   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<>();
    .........
}

然或我们通过具体对象的赋值来了解下这些参数

    

   2、主要参数值梳理:

        1、objectName : 就是对应的参数名(每个参数在解析的时候都会创建对应的WebDataBinder)。

        2、target : 代表的对应value

        3、allowedFields、disallowedFields、requiredFields就是进行校验单。

       4、bindingResult就是用来放校验失败的一些描叙信息

       5、其他的如typeConverter、conversionService,这些就是用来做类型转换的,前面的文章有介绍

   3、主要方法

        1、构造方法

public DataBinder(@Nullable Object target, String objectName) {
   this.target = ObjectUtils.unwrapOptional(target);
   this.objectName = objectName;
}

       2、initBeanPropertyAccess

   这个方法主要是创建前面的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;
}

   3、setRequiredFields(其他的disallowedFields、allowedFields)与其类似

public void setRequiredFields(@Nullable String... requiredFields) {
   this.requiredFields = PropertyAccessorUtils.canonicalPropertyNames(requiredFields);
}

    4、addCustomFormatter(这个就是添加自定义属性编辑这种,还有其他的方法也与这个类似)

public void addCustomFormatter(Formatter<?> formatter) {
   FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
   getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
}

   5、convertIfNecessary(这个就是去进行类型转换)

public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
      @Nullable MethodParameter methodParam) throws TypeMismatchException {

   return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}

  6、bind

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

   这里就是进行类型绑定,前面设置的那些requireFiled

  7、checkRequiredFields

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());
            if (pv != null) {
               mpvs.removePropertyValue(pv);
               propertyValues.remove(field);
            }
         }
      }
   }
}

这个方法就是判断mpvs中有没有getRequiredFields中的内容,如果没有为empty,就通过getBindingErrorProcessor().processMissingFieldError(field, getInternalBindingResult()),将错误描叙填充到bindResult中

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

3、WebDataBinder

    1、成员变量

public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";

public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";

@Nullable
private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;

@Nullable
private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;

private boolean bindEmptyMultipartFiles = true;

   可以看到这里主要是一些前缀的类变量

   2、方法

  1、doBind

protected void doBind(MutablePropertyValues mpvs) {
   checkFieldDefaults(mpvs);
   checkFieldMarkers(mpvs);
   super.doBind(mpvs);
}

    可以看到这里重写了doBind方法,加入了checkFieldDefaults、checkFieldMarkers,这两个就有用到前面的前缀的内容。

   2、checkFieldDefaults(checkFieldMarkers与之类似)

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

这个方法就是判断mpvs有没有以"!",开头的,如果有,并且去掉这个"!"前缀后的名称,在mpvs没有(并且这个Property是可写操作的),就将去掉前缀的field添加到mpvs中。

   例如demo

@Test
public void testFieldDefaultPreemptsFieldMarker() throws Exception {
   TestBean target = new TestBean();
   ServletRequestDataBinder binder = new ServletRequestDataBinder(target);
   MockHttpServletRequest request = new MockHttpServletRequest();
   request.addParameter("!postProcessed", "on");
   request.addParameter("_postProcessed", "visible");
   request.addParameter("postProcessed", "on");
   binder.bind(request);
}

      

3、bindMultipart

protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
   for (Map.Entry<String, List<MultipartFile>> entry : multipartFiles.entrySet()) {
      String key = entry.getKey();
      List<MultipartFile> values = entry.getValue();
      if (values.size() == 1) {
         MultipartFile value = values.get(0);
         if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
            mpvs.add(key, value);
         }
      }
      else {
         mpvs.add(key, values);
      }
   }
}

 这个就是将文件类型的MultipartFile,添加到mpvs中。(这个方法是在哪里调用的?multipartFiles是怎样获取到的)

4、WebRequestDataBinder

    WebRequestDataBinder主要是一些方法

   1、bind

public void bind(WebRequest request) {
   MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
   if (isMultipartRequest(request) && request instanceof NativeWebRequest) {
      MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
      if (multipartRequest != null) {
         bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
      }
      else {
         HttpServletRequest servletRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);
         if (servletRequest != null) {
            bindParts(servletRequest, mpvs);
         }
      }
   }
   doBind(mpvs);
}

   这里主要是判断reqeust是不是MultipartRequest,如果是就去bindpart,然后再调用doBind,同时这里也解释了两个点。一、bindMultipart方法的调用时机,入参multipartFiles是怎样获取的。二、mpvs是怎样获取的(用request的getParameterMap去构建):

public MutablePropertyValues(@Nullable Map<?, ?> original) {
   // We can optimize this because it's all new:
   // There is no replacement of existing property values.
   if (original != null) {
      this.propertyValueList = new ArrayList<>(original.size());
      original.forEach((attrName, attrValue) -> this.propertyValueList.add(
            new PropertyValue(attrName.toString(), attrValue)));
   }
   else {
      this.propertyValueList = new ArrayList<>(0);
   }
}
private final List<PropertyValue> propertyValueList;

  2、isMultipartRequest

private boolean isMultipartRequest(WebRequest request) {
   String contentType = request.getHeader("Content-Type");
   return (contentType != null && StringUtils.startsWithIgnoreCase(contentType, "multipart"));
}

  3、bindParts

private void bindParts(HttpServletRequest request, MutablePropertyValues mpvs) {
   try {
      MultiValueMap<String, Part> map = new LinkedMultiValueMap<>();
      for (Part part : request.getParts()) {
         map.add(part.getName(), part);
      }
      for (Map.Entry<String, List<Part>> entry: map.entrySet()) {
         if (entry.getValue().size() == 1) {
            Part part = entry.getValue().get(0);
            if (isBindEmptyMultipartFiles() || part.getSize() > 0) {
               mpvs.add(entry.getKey(), part);
            }
         }
         else {
            mpvs.add(entry.getKey(), entry.getValue());
         }
      }
   }
   catch (Exception ex) {
      throw new MultipartException("Failed to get request parts", ex);
   }
}

这里与前面的bindMultipart是走的两个分支,如果能转NativeWebRequest就用bindMultipart。如果是一般request,并且有文件(用isMultipartRequest判断),就通过bindParts从一般的request中获取添加到mpvs中。

 那这个WebRequestDataBinder的bind方法是在哪里调用的呢

springMVC中的ModelAttributeMethodProcessor,其supportsParameter方法:

public boolean supportsParameter(MethodParameter parameter) {
   return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
         (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}

   resolveArgument方法

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   String name = ModelFactory.getNameForParameter(parameter);
   ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
     ...........
   if (bindingResult == null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
      if (binder.getTarget() != null) {
         if (!mavContainer.isBindingDisabled(name)) {
            bindRequestParameters(binder, webRequest);
         }
         validateIfApplicable(binder, parameter);
         if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
            throw new BindException(binder.getBindingResult());
         }
      }
      if (!parameter.getParameterType().isInstance(attribute)) {
         attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
      }
      bindingResult = binder.getBindingResult();
   }
     ...........
   return attribute;
}
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    ((WebRequestDataBinder)binder).bind(request);
}

5、总结

   关于WebDataBinder这三个在比较基础的类就解析完毕(其上还有一些子类,到解析流程源码解读的时候在具体分析)。可以看到WebDataBinder如前面说的主要有两个用处:

    1、进行类型解析:通过convertIfNecessary方法。

    2、进行一些require、allow这种的校验,要使用的与@InitBInder、@ModelAttribute直接联系。间接确认使用使用到ModelAttributeMethodProcessor(HandlerMethodArgumentResolver)。

   这上面其实还有省略一个地方,就是@Validated,其是通过validateIfApplicable方法去处理,如果如果有失败,也是像requireFields类似,将错误内容放到bindResult中。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/66272
推荐阅读
  

闽ICP备14008679号