SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers: SpringMVC Parameter Parsers How to customize a parameter parser in SpringBoot? .

However, I believe that many people are really confused about the interface, such as the following parameters parsing:

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

Or, for interfaces like the following, how the parameters are resolved:

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

This is the most common way we define parameters in our daily life, and I believe many people 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 officially share, let’s take a look at the overall range of parameter parsers.

1. Parameter parser

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 group 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 return values of the corresponding types.
  • XxxAdapter: This adapter does not do parameter parsing, but only serves as a parameter resolver of type WebGumentResolver.
  • 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.

There are four broad categories, of which the first two are of course the most important.

2. An overview of parameter parsers

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

MapMethodProcessor

This is used to process parameters of type Map/ModelMap and returns Model when parsed.

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 parameters, such as bindingResult when we do parameter validation.

AbstractNamedValueMethodArgumentResolver

This is used to handle parameters of type key/value, such as request header parameters, parameters with @PathVariable annotations, 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 function is more extensive. Parameters that use the @RequestParam annotation, file upload types that are multipartFile, or basic types that don’t use any annotations (Long, Integer), String, etc., are all handled using this 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 superclass that handles parameters with the @CookieValue annotation.

ServletCookieValueMethodArgumentResolver

This processing takes the parameter 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 is used to handle parameters that use the @SessionAttribute annotation.

ExpressionValueMethodArgumentResolver

This is used to handle parameters with the @Value annotation.

ServletResponseMethodArgumentResolver

This handles parameters of type ServletResponse, OutputStream, and Writer.

ModelMethodProcessor

This handles the Model type parameter and returns the Model.

ModelAttributeMethodProcessor

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

SessionStatusMethodArgumentResolver

This is used to handle arguments of type SessionStatus.

PrincipalMethodArgumentResolver

This handles the Principal type parameters, which Songo described in the previous article (SpringBoot: How to customize the parameter parser?). ()).

AbstractMessageConverterMethodArgumentResolver

This is the parent class from which the associated processing classes are inherited when the RequestBody type parameter is resolved using HttpMessageConverter.

RequestPartMethodArgumentResolver

This handles parameters that use the @RequestPart annotation, multipartFile, and Part type.

AbstractMessageConverterMethodProcessor

This is a utility class and does not undertake the task of parsing parameters.

RequestResponseBodyMethodProcessor

This is used to handle parameters with the @RequestBody annotation added.

HttpEntityMethodProcessor

This handles parameters of the HttpEntity and RequestEntity types.

ContinuationHandlerMethodArgumentResolver

AbstractWebArgumentResolverAdapter

This does not do parameter resolution and is only used as an adapter for a parameter resolver of type WebGumentResolver.

ServletWebArgumentResolverAdapter

This provides a request to the parent class.

UriComponentsBuilderMethodArgumentResolver

This handles URIComponentsBuilder type parameters.

ServletRequestMethodArgumentResolver

This is for processing WebRequest, ServletRequest, MultipartRequest, HttpSession, Principal, InputStream, Reader, HttpMethod, Locale, TimeZone, Zoneid Parameter of type.

HandlerMethodArgumentResolverComposite

This is a composite parser. It is a proxy, a parameter parser that proxies other things.

RedirectAttributesMethodArgumentResolver

This is used to handle parameters of the redirectAttributes type, as mentioned in the previous article: Can SpringMVC parameters still be passed this way? The posture is up! .

OK, the general function of each parameter parser to introduce you, let’s choose one of them, to talk about its specific source code.

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, The resolveArgument method is implemented, so let’s look at it:

@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; }
  1. We first get a NameValueInfo object based on the current request, which holds the three properties of the parameter: the parameter name, whether the parameter is required, and the parameter default value. The specific process is to get it from the cache first. If there is one in the cache, it will return directly. If there is not one in the cache, it will call the createMenamedValueInfo method to create it, and the result will be cached and returned. The createMenamedValueInfo method is a template method that is implemented in a subclass.
  2. Next, deal with the Optional type 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);
}

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.

  1. Then we call the resolveName method, which is also a template method and is implemented in a subclass.
  2. If the parameter value is null, first check to see if there is a default value in the annotation, then check to see if the parameter value is required. If so, throw an exception, otherwise set it to null.
  3. If the value of the parsed parameter is an empty string"", will also go to resolveEmbeddedValuesAndExpressions method.
  4. Finally, there is the handling of the WebDataBinder, which addresses some of the global parameters, and the WebDataBinder has also been described in the previous article, Portal: @ControllerAdvice, in three usage scenarios.

This is the general flow.

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

  • createNamedValueInfo
  • resolveName

In addition to 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)); }

It is easy to see the supported parameter types from the SupportSparameter method:

  1. First of all, if you have a parameter@RequestParamIf the parameter type is Map, then@RequestParamAnnotations must be configured with the name attribute, otherwise they are not supported; If the parameter is not of a Map type, it simply returns true, indicating that it is always supported (think about The Times you use it).
  2. If the parameter contains@RequestPartAnnotation is not supported.
  3. Check if a file upload request was requested. If so, return true to indicate support.
  4. If none is returned, the default solution is used to determine if it is a simple type, which is Void, enumeration, string, number, date, and so on.

This piece of code is very simple. It’s easy to see who is supported and who is not.

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() {
        super("", false, ValueConstants.DEFAULT_NONE);
    }
    public RequestParamNamedValueInfo(RequestParam annotation) {
        super(annotation.name(), annotation.required(), annotation.defaultValue());
    }
}

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; }

This method is also clear:

  1. The first two ifs are primarily used to handle file upload requests.
  2. Call if it is not a file upload requestrequest.getParameterValuesMethod retrieves the parameter and returns.

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

5. Summary

In this article, we will review the entire SpringMVC parameter parser system. We will discuss when these parsers are configured and when they are invoked. Well, that’s all for today.