`

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
分享到:
评论

相关推荐

    hessian-lite-3.2.1-fixed-2.jar

    com.alibaba:hessian-lite:jar:3.2.1-fixed-2 hessian-lite hessian-lite-3.2.1-fixed-2.jar

    spring-framework-3.2.1--3.x

    本文将围绕"spring-framework-3.2.1.RELEASE"这一版本,详细探讨Spring框架3.x系列的核心特性和使用方法。 一、Spring概述 Spring是一个全面的后端应用程序开发框架,它提倡依赖注入(Dependency Injection, DI)和...

    spark-3.2.1-bin-hadoop2.7.tgz

    总结一下,"spark-3.2.1-bin-hadoop2.7.tgz"是一个专为Linux设计的Spark版本,与Hadoop 2.7兼容,提供了高效的大数据处理能力,涵盖了核心计算、SQL查询、流处理、机器学习和图计算等多个方面。在实际应用中,开发者...

    helm-v3.2.1-linux-amd64.tar.gz

    标题 "helm-v3.2.1-linux-amd64.tar.gz" 暗示着这是一个包含 Helm v3.2.1 版本的软件包,适用于 Linux 平台的 x86_64(AMD64)架构。Helm 是 Kubernetes(K8s)生态系统中的一个关键组件,用作管理 Kubernetes 应用...

    gradle-3.2.1-all.zip,完整版-解压即可使用

    Gradle 的核心特性之一是它的基于Groovy的领域特定语言(DSL),这种DSL使得构建脚本更加简洁易读,允许开发者用面向对象的方式描述项目构建过程。在Gradle-3.2.1中,你可以通过编写`build.gradle`文件来定义项目的...

    isparta-3.2.1-ia32-win.zip

    iSparta是一款专为Windows平台设计的代码覆盖率工具,基于开源的Istanbul进行开发,主要服务于JavaScript项目。它的核心功能是帮助开发者在测试过程中了解代码的覆盖情况,即哪些代码被执行过,哪些还未被触及,这...

    apache-dolphinscheduler-3.2.1-src.tar.gz、bin.tar.gz

    1. **Apache DolphinScheduler源码包(apache-dolphinscheduler-3.2.1-src.tar.gz)** 这个源码包包含了DolphinScheduler项目的全部源代码,是开发人员进行二次开发、定制或者深入理解其内部机制的重要资源。解压后...

    twrp-pixel-installer-sailfish-3.2.1-0.zip

    标题 "twrp-pixel-installer-sailfish-3.2.1-0.zip" 暗示了这是一款针对Pixel设备的TWRP(Team Win Recovery Project)恢复环境的安装程序,版本为3.2.1-0,而"Sailfish"可能是该版本的一个特定代号或特色。...

    commons-collections-3.2.1-API文档-中文版.zip

    赠送原API文档:commons-collections-3.2.1-javadoc.jar; 赠送源代码:commons-collections-3.2.1-sources.jar; 包含翻译后的API文档:commons-collections-3.2.1-javadoc-API文档-中文(简体)版.zip 对应Maven...

    cmake-3.2.1-win32-x86.exe

    cmake-3.2.1-win32-x86.exe Windows下的编译工具

    apache-maven-3.2.1-bin.zip

    apache-maven-3.2.1-bin.zip

    pyproj-3.2.1-cp39-cp39-win_amd64.whl.zip

    python.exe

    isparta-3.2.1-win.rar

    【标题】"isparta-3.2.1-win.rar" 指的是一个名为 "isparta" 的工具的Windows版本,版本号为3.2.1,并且该工具的安装或打包文件以RAR压缩格式提供。RAR是一种常见的文件压缩格式,用于减少文件大小以便于存储和传输...

    postgis-bundle-pg10x64-setup-3.2.1-1.exe安装包,对应postgres10

    postgis-bundle-pg10x64-setup-3.2.1-1.exe安装包,对应postgres10

    cas-client-core-3.2.1-API文档-中英对照版 (1).zip

    赠送原API文档:cas-client-core-3.2.1-javadoc.jar; 赠送源代码:cas-client-core-3.2.1-sources.jar; 赠送Maven依赖信息文件:cas-client-core-3.2.1.pom; 包含翻译后的API文档:cas-client-core-3.2.1-javadoc...

    NessusClient-3.2.1-es5.i386.rpm

    NessusClient-3.2.1-es5.i386.rpm NessusClient-3.2.1-es5.i386.rpm

    commons-collections-3.2.1-API文档-中英对照版.zip

    赠送原API文档:commons-collections-3.2.1-javadoc.jar 赠送源代码:commons-collections-3.2.1-sources.jar 包含翻译后的API文档:commons-collections-3.2.1-javadoc-API文档-中文(简体)-英语-对照版.zip 对应...

    NLpack1-eclipse-SDK-3.2.1-win32汉化包

    NLpack1-eclipse-SDK-3.2.1-win32汉化包

    networkx-3.2.1-py3-none-any.whl

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、...

Global site tag (gtag.js) - Google Analytics