@ModelAttribute in Spring

Previously we approached the idea of Spring validation with annotations. We mentioned several lines about @ModelAttribute annotations. But it's still not sufficient to understand well this concept.

This article will cover @ModelAttribute resolving. It'll be composed by two parts. The first one will present the general utility of this annotation. The second part will present code details of this annotations and its resolvers.

What is @ModelAttribute annotation ?

The main purpose of @ModelAttribute annotation is to "translate" request into object specified with this annotation. For example, if you specify an Article instance next to @ModelAttribute, all request parameters corresponding to Article's fields, will be used as Article's fields values. That means, for example, that a POST parameter title value will be set to Article's title field.

So, this annotation allows the developers to make persist an object across requests. Without it, Spring considers that a new object must be created. Additionally, it exposes an object model directly to view. You don't need anymore to make a model.setAttribute() calls inside a method. In the view part, the object can be retrieved by specified value inside annotation (for example the @ModelAttribute("articleView") can be retrieved on view with ${articleView} variable) or by the object's class name (for example the @ModelAttribute() Article article will expose Article's instance as ${article} variable).

@ModelAttribute annotation code details

Three key classes participate in @ModelAttribute translation into expected object. The first one is org.springframework.web.bind.annotation.support.HandlerMethodResolver. It contains a private field of Set type, called modelAttributeMethods. This field contains annotated with @ModelAttribute. In init() method, the resolver places all concerned methods inside this Set.

After that, org.springframework.web.bind.annotation.support.HandlerMethodInvoker starts to play. In its method invokeHandlerMethod(), it retrieves all method from modelAttributeMethods Set. If model attribute wasn't resolved previously, it creates the object by binding request parameters to object's fields.

Class that makes the binding is org.springframework.web.method.annotation.ModelAttributeMethodProcessor. More precisely, it's the method protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) that binds the request to target object. And to be more precise, it uses WebRequestDataBinder's bind() method to do that.

Fields value setter is specified on DataBinder's applyPropertyValues method:

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

First, it gets an implementation of org.springframework.beans.AbstractPropertyAccessor class (getPropertyAccessor). After that, it puts the values found inside HTTP request into resolved object through concrete implementation of this abstract method public void setPropertyValue(String propertyName, Object value). This method is implemented by both BeanWrapperImpl and DirectFieldAccessor classes from the org.springframework.beans package. By default, the class used by ModelAttributeMethodProcessor is org.springframework.beans.BeanWrapperImpl this is a default implementation of BeanWrapper. This default implementation can both set and get the bean's properties (classes fields). It implements a setPropertyValue method in this way:

@Override
public void setPropertyValue(String propertyName, Object value) throws BeansException {
	BeanWrapperImpl nestedBw;
	try {
		nestedBw = getBeanWrapperForPropertyPath(propertyName);
	}
	catch (NotReadablePropertyException ex) {
		throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
				"Nested property in path '" + propertyName + "' does not exist", ex);
	}
	PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
	nestedBw.setPropertyValue(tokens, new PropertyValue(propertyName, value));
}

The values set is moved to private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) that won't be exposed here in details. You need only to know that it's a method which sets a value of one field. The set field can be a simple type (String, int and so on), but also a collection (List, Map).

This article explained about @ModelAttribute resolving in Spring web application world. As you could see the basic flow of code execution starts with HandlerMethodResolver object and ends on optional object resolving by ModelAttributeMethodProcessor instance. The whole process is based on data binding, implemented in DataBinder subclasses. They use property accessors (the default BeanWrapperImpl) to take the key-value pair from the request and put it to target object.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!