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
- 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.
- Next, deal with the Optional parameter.
- 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:
- First of all, if there are parameters
@RequestParam
Annotation, there are two cases: parameter type if Map, then@RequestParam
Annotations 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). - Parameter if contains
@RequestPart
Annotations are not supported. - Check if file upload is requested. If yes, return true.
- 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:
- The first two IFs are mainly for file upload requests.
- If it is not a file upload request
request.getParameterValues
Method 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.