We talked about the custom SpringMVC parameter parser in the front, and we also analyzed several relatively simple parameter parser, I believe you should have a certain understanding of SpringMVC parameter parser, if you have not seen the partner can have a look first: How to customize the parameter parser in SpringBoot? .

However, I believe that many people are really confused by the interface like the following, how to parse the parameters:

@GetMapping("/hello2")
public void hello2(String name) {
    System.out.println("name = " + name);
}
Copy the code

Or how parameters are parsed in interfaces like the following:

@GetMapping("/hello/{id}")
public void hello3(@PathVariable Long id) {
    System.out.println("id = " + id);
}
Copy the code

This is the most common way to define parameters in our daily life. I believe many friends are interested in this. Because this involves a very large class AbstractNamedValueMethodArgumentResolver, so here I am alone wrote an article to share with you this problem.

Before we share, let’s take a look at what parameter parsers are all about.

1. Parameter resolver

HandlerMethodArgumentResolver parser is our claim parameters, its implementation class very much, because each type of parameters are corresponding to a parser:

For ease of understanding, we can classify these parameter parsers into four broad categories:

  • XxxMethodArgumentResolver: this is a common parameter parser.
  • XxxMethodProcessor: Not only can it be used as a parameter parser, but it can also handle the return values of the corresponding types.
  • XxxAdapter: An adapter that does no parameter parsing and is used only as a parameter parser of type WebArgumentResolver.
  • HandlerMethodArgumentResolverComposite: this name will know that is a composite parser, it is an agent, specific agent for the other work of the parameters of the parser.

It can be roughly divided into four categories, among which the first two are of course the most important.

2. Overview of parameter parsers

Let’s take a look at what each of these parameter parsers is used for.

MapMethodProcessor

This is used to process Map/ModelMap parameters and return model after parsing.

PathVariableMethodArgumentResolver

This is used to deal with using the @ PathVariable annotations and parameter types for the parameters of the Map, PathVariableMapMethodArgumentResolver parameter type for the Map is used to deal with.

PathVariableMapMethodArgumentResolver

See above.

ErrorsMethodArgumentResolver

This is used to handle Error arguments, such as BindingResult when we do parameter verification.

AbstractNamedValueMethodArgumentResolver

This handles key/value parameters, such as request header parameters, @pathvariable annotated parameters, and cookies.

RequestHeaderMethodArgumentResolver

This is used to deal with using the @ RequestHeader annotations, and parameter types is not a Map of parameters (parameter types are the Map use RequestHeaderMapMethodArgumentResolver).

RequestHeaderMapMethodArgumentResolver

See above.

RequestAttributeMethodArgumentResolver

This is used to handle parameters that use the @requestAttribute annotation.

RequestParamMethodArgumentResolver

This is a little bit more extensive. Parameters that use the @requestParam annotation, the file upload type MultipartFile, or some primitive types (Long, Integer, String) that do not use any annotations, are all handled by the parameter parser. It is important to note that if the @ RequestParam annotations parameter types is the Map, then the annotation must have a name value, otherwise will be completed by RequestParamMapMethodArgumentResolver.

RequestParamMapMethodArgumentResolver

See above.

AbstractCookieValueMethodArgumentResolver

This is a parent class that handles arguments that use the @cookievalue annotation.

ServletCookieValueMethodArgumentResolver

This process uses the parameters of the @cookievalue annotation.

MatrixVariableMethodArgumentResolver

This processing using the @ MatrixVariable annotations and parameter type is not the parameters of the Map, if the parameter type is the Map, use the MatrixVariableMapMethodArgumentResolver to deal with.

MatrixVariableMapMethodArgumentResolver

See above.

SessionAttributeMethodArgumentResolver

This handles arguments that use the @sessionAttribute annotation.

ExpressionValueMethodArgumentResolver

This is used to handle arguments that use the @value annotation.

ServletResponseMethodArgumentResolver

This handles ServletResponse, OutputStream, and Writer type arguments.

ModelMethodProcessor

This is used to process Model type parameters and return Model.

ModelAttributeMethodProcessor

This is used to process parameters that use the @modelAttribute annotation.

SessionStatusMethodArgumentResolver

This is used to handle arguments of type SessionStatus.

PrincipalMethodArgumentResolver

This is used to handle Principal type parameters, which Songo explained in a previous article. .

AbstractMessageConverterMethodArgumentResolver

This is a parent class that the associated processing classes inherit from when a RequestBody type parameter is parsed using HttpMessageConverter.

RequestPartMethodArgumentResolver

This is used to handle parameters that use the @requestPart annotation, MultipartFile, and Part type.

AbstractMessageConverterMethodProcessor

This is a utility class that does not handle parameter parsing.

RequestResponseBodyMethodProcessor

This is used to handle arguments with the @requestBody annotation added.

HttpEntityMethodProcessor

This parameter is used to handle HttpEntity and RequestEntity types.

ContinuationHandlerMethodArgumentResolver

AbstractWebArgumentResolverAdapter

This does not do parameter parsing and is only used as an adapter for parameter parsers of type WebArgumentResolver.

ServletWebArgumentResolverAdapter

This provides a request to the parent class.

UriComponentsBuilderMethodArgumentResolver

This is used to handle arguments of type UriComponentsBuilder.

ServletRequestMethodArgumentResolver

This is used to process WebRequest, ServletRequest, MultipartRequest, HttpSession, Principal, InputStream, Reader, HttpMethod, Locale, TimeZone, ZoneId Type parameter.

HandlerMethodArgumentResolverComposite

This, as the name suggests, is a composite parser, which is a proxy for those parameter parsers that do other work.

RedirectAttributesMethodArgumentResolver

This is used to handle parameters of the RedirectAttributes type. RedirectAttributes explained in a previous article: Can parameters in SpringMVC still be passed this way? It’s up! .

Ok, the general function of each parameter parser is introduced to you, next we choose one of them, to talk about its source code in detail.

3.AbstractNamedValueMethodArgumentResolver

AbstractNamedValueMethodArgumentResolver is an abstract class, some key parameters of type parser is through inheritance to achieve it, and it defines many of these keys to type parameters inside the parser’s public operations.

AbstractNamedValueMethodArgumentResolver is applied in many template pattern, such as it does not implement supportsParameter method, the implementation of this method in different subclasses, ResolveArgument ();

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
	MethodParameter nestedParameter = parameter.nestedIfOptional();
	Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
	if (resolvedName == null) {
		throw new IllegalArgumentException(
				"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
	}
	Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
	if (arg == null) {
		if(namedValueInfo.defaultValue ! =null) {
			arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
		}
		else if(namedValueInfo.required && ! nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); } arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); }else if ("".equals(arg) && namedValueInfo.defaultValue ! =null) {
		arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
	}
	if(binderFactory ! =null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		try {
			arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
		}
		catch (ConversionNotSupportedException ex) {
			throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
		catch (TypeMismatchException ex) {
			throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
		// Check for null value after conversion of incoming argument value
		if (arg == null && namedValueInfo.defaultValue == null&& namedValueInfo.required && ! nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); } } handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;
}
Copy the code
  1. We first get a NamedValueInfo object based on the current request, which holds three properties of the parameter: the parameter name, whether the parameter is required, and the parameter default value. If there is one in the cache, it will be returned directly. If there is none in the cache, the createNamedValueInfo method will be called to create it. The result will be cached and returned. The createNamedValueInfo method is a template method that is implemented in a subclass.
  2. Next, deal with the Optional parameter.
  3. ResolveEmbeddedValuesAndExpressions method is used to deal with annotations SpEL expression, such as the following interface:
@GetMapping("/hello2")
public void hello2(@RequestParam(value = "${aa.bb}") String name) {
    System.out.println("name = " + name);
}
Copy the code

Parameter name using the expression, then resolveEmbeddedValuesAndExpressions method is the purpose of parsing out the value of the expression, if useless to the expression, the method will return the original parameters of the intact. 4. Then call the resolveName method to resolve the specific value of the parameter. This method is also a template method, the specific implementation of the subclass. 5. If the obtained parameter value is NULL, check whether there is a default value in the annotation, and then check whether the parameter value is required. If yes, throw an exception, otherwise set it to NULL. 6. If the parsing out the values of the parameters to the empty string “”, will also go to resolveEmbeddedValuesAndExpressions method. 7. Finally, there is the WebDataBinder processing, which resolves some global parameter issues. WebDataBinder songo also covered portal: @ControllerAdvice in three usage scenarios.

That’s the general process.

In this flow, we see the following two main methods implemented in subclasses:

  • createNamedValueInfo
  • resolveName

Along with the supportsParameter method, there are three methods in the subclass that we need to focus on.

Then we’ll RequestParamMethodArgumentResolver, for example, look at these three methods.

4.RequestParamMethodArgumentResolver

4.1 supportsParameter

@Override
public boolean supportsParameter(MethodParameter parameter) {
	if (parameter.hasParameterAnnotation(RequestParam.class)) {
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
			return(requestParam ! =null && StringUtils.hasText(requestParam.name()));
		}
		else {
			return true; }}else {
		if (parameter.hasParameterAnnotation(RequestPart.class)) {
			return false;
		}
		parameter = parameter.nestedIfOptional();
		if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
			return true;
		}
		else if (this.useDefaultResolution) {
			return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
		}
		else {
			return false; }}}public static boolean isSimpleProperty(Class
        type) {
	return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}
public static boolean isSimpleValueType(Class
        type) {
	return(Void.class ! = type &&void.class ! = type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) ||  Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type)); }Copy the code

The supportsParameter method provides a handy indication of the supported parameter types:

  1. First of all, if there are parameters@RequestParamAnnotation, there are two cases: parameter type if Map, then@RequestParamAnnotations must be configured with the name attribute, otherwise they are not supported. If the parameter type is not Map, return true, indicating that it is always supported (think about your usual usage).
  2. Parameter if contains@RequestPartAnnotations are not supported.
  3. Check if file upload is requested. If yes, return true.
  4. If none of the above returns, the default solution is used to determine whether it is a simple type, mainly Void, enumeration, string, number, date, and so on.

This code is actually very simple, support who do not support who, at a glance.

4.2 createNamedValueInfo

@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
	RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
	return(ann ! =null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
private static class RequestParamNamedValueInfo extends NamedValueInfo {
	public RequestParamNamedValueInfo(a) {
		super("".false, ValueConstants.DEFAULT_NONE);
	}
	public RequestParamNamedValueInfo(RequestParam annotation) {
		super(annotation.name(), annotation.required(), annotation.defaultValue()); }}Copy the code

To obtain annotations, read comments in the property, structure RequestParamNamedValueInfo object to return.

4.3 resolveName

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
	HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
	if(servletRequest ! =null) {
		Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
		if(mpArg ! = MultipartResolutionDelegate.UNRESOLVABLE) {return mpArg;
		}
	}
	Object arg = null;
	MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
	if(multipartRequest ! =null) {
		List<MultipartFile> files = multipartRequest.getFiles(name);
		if(! files.isEmpty()) { arg = (files.size() ==1 ? files.get(0) : files); }}if (arg == null) {
		String[] paramValues = request.getParameterValues(name);
		if(paramValues ! =null) {
			arg = (paramValues.length == 1 ? paramValues[0] : paramValues); }}return arg;
}
Copy the code

This method is also quite clear:

  1. The first two IFs are mainly for file upload requests.
  2. If it is not a file upload requestrequest.getParameterValuesMethod take out the parameter and return.

The whole process is relatively easy. Friends can on this basis to analyze PathVariableMethodArgumentResolver principle, is also very easy.

5. Summary

Today, I mainly combed the whole system of SpringMVC parameter parsers with my friends. As for when these parsers are configured and when they are called, Songgo will continue to analyze them with you in the later articles. All right, that’s all for today.