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 { ListpropertyAccessExceptions = null; List 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); } }
往下跟踪就是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