spring源码学习系列3.2.1-command对象的绑定

在<spring源码学习系列3.2-handlerAdapter执行>中MultiActionController#invokeNamedMethod 方法中,将request参数值设置到command对象中-另一种形式的模型驱动


springmvc设计的一个很重要的原则是开闭原则:
对修改或处理流程关闭,对扩展开放
但对于自定义controller继承MultiActionController,覆盖一些方法,改变了springmvc的部分功能
如覆盖bind(HttpServletRequest request, Object command),绑定过程完全由用户的编程能力决定。

spring版本3.2.2


绑定入口接口:
ServletRequestDataBinder



日期格式转换在哪?
binder可以注册属性编辑器-将字符串格式转成成Data格式
@InitBinder
    public void initBinder(WebDataBinder binder) {
    	DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        CustomDateEditor dateEditor = new CustomDateEditor(df, true); 
        binder.registerCustomEditor(Date.class, dateEditor);
    }


参数校验在什么时候?






// If last parameter isn't of HttpSession type, it's a command.  
            if (paramTypes.length >= 3 &&  
                    !paramTypes[paramTypes.length - 1].equals(HttpSession.class)) {  
                Object command = newCommandObject(paramTypes[paramTypes.length - 1]);  
                params.add(command);  
                bind(request, command);  
            }



MultiActionController#bind
protected void bind(HttpServletRequest request, Object command) throws Exception {
		logger.debug("Binding request parameters onto MultiActionController command");
// 1 初始化ServletRequestDataBinder 
		ServletRequestDataBinder binder = createBinder(request, command);
// 2 设置request参数到command
		binder.bind(request);
		if (this.validators != null) {
			for (Validator validator : this.validators) {
				if (validator.supports(command.getClass())) {
					ValidationUtils.invokeValidator(validator, command, binder.getBindingResult());
				}
			}
		}
		binder.closeNoCatch();
	}

自定义validators,校验参数,必填校验等


MultiActionController#createBinder
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object command) throws Exception {
// 1.1 实例化ServletRequestDataBinder
		ServletRequestDataBinder binder = new ServletRequestDataBinder(command, getCommandName(command));
// 1.2 初始化binder-用户自定义-webBindingInitializer-springmvc扩展点
		initBinder(request, binder);
		return binder;
	}

对于MultiActionController中的方法,如bind,initBinder等可被自定义的controller子类覆盖,从而定义自己的绑定逻辑.但一般也不这么做,这样项目跟springmvc耦合性加强了
@Override
    protected void bind(HttpServletRequest request, Object command) throws Exception {
    	System.out.println("自定义绑定方法bind");
    }
    
    @Override
    protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object command) throws Exception {
    	return null;
    }
    
    @Override
    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    	binder.setAutoGrowNestedPaths(true);  
        binder.setAutoGrowCollectionLimit(1024);  
    	System.out.println("hi, i am initBinder");
    }



ServletRequestDataBinder继承体系为:
ServletRequestDataBinder extends WebDataBinder extends DataBinder
创建ServletRequestDataBinder的对象时,用要设置值的command对象初始化binder。即将command封装到DataBinder


=============================================职责分割线
实际绑定command对象的工作交由DataBinder接口去完成,用户(开发人员)只需要将设置值request及被设置值command传给DataBinder就行
1.request->MutalbPropertyValues(dataBinder)
2.MutalbPropertyValues->command(beanWrapper)



ServletRequestDataBinder#bind
public void bind(ServletRequest request) {
// 2.1 创建MutablePropertyValues:将request值设置到ProperValues里面
// 循环request.getParameterNames()的参数放入map,并初始化MutablePropertyValues
		MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
		MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
		if (multipartRequest != null) {
			bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
		}
		addBindValues(mpvs, request);
// 2.2 委托父类方法,属性值mpvs设置到command对象-已经在实例化ServletRequestDataBinder时包含
		doBind(mpvs);
	}



WebDataBinder#doBind
@Override
	protected void doBind(MutablePropertyValues mpvs) {
// 2.2.1 处理特殊属性名(以! 或者 _开头)的属性名,去掉前缀-WebDataBinder绑定前校验
// 用户(开发人员)可在自定义的controller下,覆盖createBinder方法,改变这个地方的逻辑
		checkFieldDefaults(mpvs);
		checkFieldMarkers(mpvs);
// 2.2.2 委托父类方法,实际绑定command对象
		super.doBind(mpvs);
	}

2.2.1 处理特殊属性名,去掉前缀。如果不需要这个逻辑,可覆盖createBinder,自定义自己的dataBinder extexds ServletRequestDataBinder


DataBinder#doBind
/**
	 * 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) {
// 2.2.2.1 检查是否自定义允许解析的属性名-DataBinder绑定前校验
		checkAllowedFields(mpvs);
		checkRequiredFields(mpvs);
// 2.2.2.2 执行绑定
		applyPropertyValues(mpvs);
	}

DataBinder#doBind是绑定mpvs到command对象的地方,也许这就是将request参数封装到MutablePropertyValues的意义。ServletRequestDataBinder只是封装request到mpvs,也有其他形式的请求参数,只需要继承DataBinder,然后做相应的封装

对于检验的错误或其他信息的处理,springmvc是将其放到DataBinder的属性bindingResult中,其为类BeanPropertyBindingResult的对象


对于2.2.1与2.2.2.1绑定前校验,整合起来看,为什么不将她们的校验放在一起。应该独立来看,每个类的关注点或职责不一样,她们只需要保持各自方法的健壮性


DataBinder#applyPropertyValues
protected void applyPropertyValues(MutablePropertyValues mpvs) {
		try {
// 2.2.2.2.1 
			// 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());
			}
		}
	}

getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());为异常处理机制,将异常设置到bindingResult,在绑定结束时,会调用ServletRequestDataBinder#closeNoCatch判断是否有异常,有则抛出


request->mpvs
=============================================职责分割线
DataBinder最终还是将指挥棒交给了BeanWrapper
mpvs->command


AbstractPropertyAccessor#setPropertyValues
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<PropertyAccessException>();
				}
				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);
		}
	}

往下跟踪就是beanWrapper如何设置属性值了,离最初的主题springmvc原理越来越远了,所以不再往下了




以下为得到beanWrapper或ConfigurablePropertyAccessor的路径-可忽略
DataBinder#getPropertyAccessor
protected ConfigurablePropertyAccessor getPropertyAccessor() {
		return getInternalBindingResult().getPropertyAccessor();
	}


DataBinder#getInternalBindingResult
protected AbstractPropertyBindingResult getInternalBindingResult() {
		if (this.bindingResult == null) {
			initBeanPropertyAccess();
		}
		return this.bindingResult;
	}


DataBinder#initBeanPropertyAccess
public void initBeanPropertyAccess() {
		Assert.state(this.bindingResult == null,
				"DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
		this.bindingResult = new BeanPropertyBindingResult(
				getTarget(), getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
		if (this.conversionService != null) {
			this.bindingResult.initConversion(this.conversionService);
		}
	}



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) {
			this.beanWrapper = createBeanWrapper();
			this.beanWrapper.setExtractOldValueForEditor(true);
			this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
			this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
		}
		return this.beanWrapper;
	}


BeanPropertyBindingResult#createBeanWrapper
protected BeanWrapper createBeanWrapper() {
		Assert.state(this.target != null, "Cannot access properties on null bean instance '" + getObjectName() + "'!");
		return PropertyAccessorFactory.forBeanPropertyAccess(this.target);
	}


PropertyAccessorFactory#forBeanPropertyAccess
public static BeanWrapper forBeanPropertyAccess(Object target) {
		return new BeanWrapperImpl(target);
	}





感想:
如果不用command绑定,正常的request.getParameter(),效率肯定比springmvc的参数绑定效率高,空间上也占用比较少的内存.
那么在时间和空间上,有哪些区别呢,对于command绑定:
创建了ServletRequestDataBinder对象
创建了MutablePropertyValues对象-用于包装request
创建了BeanPropertyBindingResult
创建了DefaultBindingErrorProcessor
创建了BeanWrapperImpl-设置command属性






参考:
Validation, Data Binding, and Type Conversion
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html

猜你喜欢

转载自newjava-sina-cn.iteye.com/blog/2376240