赞
踩
要了解这部分需要知道@ModelAttribute的基本使用
我们理解下这个WebDataBinder是用在哪里、是做什么用的。通过前一章 我们有理解这个WebDataBinder是有WebDataBinderFactory去创建的。所以,我们直接看其WebDataBinderFactory的createBinder方法的调用:
可以看到在这些地方有调用,同时通过前面章节梳理HandlerMethodArgumentResolver、及HandlerMethodResultResolver接口我们可以知道,这里的Resolver、Processor为后缀的类都是归属与这种,这种就是在解析对应参数的时候,用这个WebDataBinder去对获取到的初始解析类型转换、一些其他的校验处理:
每个参数都创建一个对应的WebDataBinder,因为这里是在HandlerMethodArgumentResolver接口的resolveArgument调用的createBinder方法
@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"设置的必须的,所以不通过。下面我们来看下具体的类
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<>(); ......... }
然或我们通过具体对象的赋值来了解下这些参数
1、objectName : 就是对应的参数名(每个参数在解析的时候都会创建对应的WebDataBinder)。
2、target : 代表的对应value
3、allowedFields、disallowedFields、requiredFields就是进行校验单。
4、bindingResult就是用来放校验失败的一些描叙信息
5、其他的如typeConverter、conversionService,这些就是用来做类型转换的,前面的文章有介绍
public DataBinder(@Nullable Object target, String objectName) { this.target = ObjectUtils.unwrapOptional(target); this.objectName = objectName; }
这个方法主要是创建前面的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; }
public void setRequiredFields(@Nullable String... requiredFields) { this.requiredFields = PropertyAccessorUtils.canonicalPropertyNames(requiredFields); }
public void addCustomFormatter(Formatter<?> formatter) { FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter); getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter); }
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType, methodParam); }
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
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); }
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;
可以看到这里主要是一些前缀的类变量
protected void doBind(MutablePropertyValues mpvs) { checkFieldDefaults(mpvs); checkFieldMarkers(mpvs); super.doBind(mpvs); }
可以看到这里重写了doBind方法,加入了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); }
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是怎样获取到的)
WebRequestDataBinder主要是一些方法
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;
private boolean isMultipartRequest(WebRequest request) { String contentType = request.getHeader("Content-Type"); return (contentType != null && StringUtils.startsWithIgnoreCase(contentType, "multipart")); }
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); }
关于WebDataBinder这三个在比较基础的类就解析完毕(其上还有一些子类,到解析流程源码解读的时候在具体分析)。可以看到WebDataBinder如前面说的主要有两个用处:
1、进行类型解析:通过convertIfNecessary方法。
2、进行一些require、allow这种的校验,要使用的与@InitBInder、@ModelAttribute直接联系。间接确认使用使用到ModelAttributeMethodProcessor(HandlerMethodArgumentResolver)。
这上面其实还有省略一个地方,就是@Validated,其是通过validateIfApplicable方法去处理,如果如果有失败,也是像requireFields类似,将错误内容放到bindResult中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。