Spring source code learning series 3.2.1-binding of command objects

In the MultiActionController#invokeNamedMethod method in <spring source code learning series 3.2-handlerAdapter execution>, set the request parameter value to the command object-another form of model-driven


springmvc design. A very important principle is the open-closed principle:
to modify Or the processing flow is closed and open to extensions.
But for custom controllers that inherit MultiActionController, override some methods, and change some functions of springmvc,
such as overriding bind (HttpServletRequest request, Object command), the binding process is completely determined by the user's programming ability.

Spring version 3.2.2


binding entry interface: Where is
ServletRequestDataBinder



date format conversion?
Binder can register property editor - convert string format to Data format
@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);
    }


When is the parameter check?






// 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 Initialize ServletRequestDataBinder
		ServletRequestDataBinder binder = createBinder(request, command);
// 2 Set the request parameter to 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();
	}

Customize validators, validation parameters, required validation, etc.


MultiActionController#createBinder
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object command) throws Exception {
// 1.1 Instantiate ServletRequestDataBinder
		ServletRequestDataBinder binder = new ServletRequestDataBinder(command, getCommandName(command));
// 1.2 Initialize binder-user-defined-webBindingInitializer-springmvc extension point
		initBinder(request, binder);
		return binder;
	}

For the methods in MultiActionController, such as bind, initBinder, etc., can be overridden by custom controller subclasses to define their own binding logic . But generally do not do this, so that the coupling between the project and springmvc is strengthened
@Override
    protected void bind(HttpServletRequest request, Object command) throws Exception {
    	System.out.println("Custom binding method 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");
    }



The inheritance system of ServletRequestDataBinder is:
ServletRequestDataBinder extends WebDataBinder extends DataBinder
When creating an object of ServletRequestDataBinder, initialize the binder with the command object whose value is to be set. About to encapsulate command to DataBinder


=============================================== Responsibility separation line The
actual work of binding the command object is done by the DataBinder interface. The user (developer) only needs to pass the set value request and the set value command to the DataBinder.
1.request->MutalbPropertyValues(dataBinder)
2.MutalbPropertyValues- >command(beanWrapper)



ServletRequestDataBinder#bind
public void bind(ServletRequest request) {
// 2.1 Create MutablePropertyValues: set the request value to ProperValues
// Loop the parameters of request.getParameterNames() into map, and initialize 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 Delegate the parent class method, the attribute value mpvs is set to the command object - already included when instantiating ServletRequestDataBinder
		doBind(mpvs);
	}



WebDataBinder # doBind
@Override
	protected void doBind(MutablePropertyValues mpvs) {
// 2.2.1 Process attribute names with special attribute names (starting with ! or _), remove the prefix -WebDataBinder before binding
// The user (developer) can override the createBinder method under the custom controller to change the logic of this place
		checkFieldDefaults(mpvs);
		checkFieldMarkers(mpvs);
// 2.2.2 Delegate the parent class method and actually bind the command object
		super.doBind(mpvs);
	}

2.2.1 Handle special attribute names and remove the prefix. If you don't need this logic, you can override createBinder and customize your own 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 Check whether to customize the property name that allows parsing - DataBinder validation before binding
		checkAllowedFields(mpvs);
		checkRequiredFields(mpvs);
// 2.2.2.2 Execute binding
		applyPropertyValues(mpvs);
	}

DataBinder#doBind is where mpvs is bound to the command object, maybe this is the meaning of encapsulating the request parameters into MutablePropertyValues. ServletRequestDataBinder only encapsulates the request to mpvs, and there are other forms of request parameters. It only needs to inherit DataBinder, and then do the corresponding encapsulation

for the processing of inspection errors or other information. Springmvc puts it in the attribute bindingResult of DataBinder, which is the class BeanPropertyBindingResult. The objects


of 2.2.1 and 2.2.2.1 are checked before binding, so why not put their checks together? It should be viewed independently, each class has different concerns or responsibilities, they only need to maintain the robustness of their respective methods


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()); is an exception handling mechanism, set the exception to bindingResult, when the binding ends, it will call ServletRequestDataBinder#closeNoCatch to judge whether there is an exception, and throw


request->mpvs
== ============================================ Responsibility dividing line
DataBinder will eventually The baton is handed over to the 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);
		}
	}

Tracking down is how beanWrapper sets property values, which is getting farther and farther from the original theme springmvc principle, so no longer go down




The following is the path to get beanWrapper or ConfigurablePropertyAccessor - ignore
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);
	}





Thoughts:
If you don't use command binding, the normal request.getParameter() is more efficient than springmvc's parameter binding, and it also occupies less memory in space.
So what are the differences in time and space? For command Binding:
Create a ServletRequestDataBinder object
Create a MutablePropertyValues ​​object - used to wrap the request
Create a BeanPropertyBindingResult
Create a DefaultBindingErrorProcessor
Create a BeanWrapperImpl - Set the command property






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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326443342&siteId=291194637