Common determination methods of parameters

Main code of parameter resolution:

   protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        MethodParameter[] parameters = this.getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        } else {
            Object[] args = new Object[parameters.length];

            for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
                if (args[i] == null) {
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (this.logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                this.logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }

                        throw var10;
                    }
                }
            }

            return args;
        }
    }

Implement the logic, judge the number of parameters in the handler method parameters, and then new an array to hold the parsed values of the parameters:

If there are no parameters, it will be returned directly. If there are parameters, first find out which parameter resolution supports the resolution of the current type; If supported, the parameter parser of the response will be used for parsing;

If the corresponding parameter parser is not found, an appropriate parameter parser will be thrown.

Here are some common notes:

1,@RequestPram

    @RequestMapping("testPram")
    public String testPram(@RequestParam("num") Integer num){
        System.out.println("The received request parameters are:"+num);
        return "success";
    }

Place a breakpoint to observe:

Then, let's look at how to judge whether the corresponding operation is supported:

    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
        if (result == null) {
            Iterator var3 = this.argumentResolvers.iterator();

            while(var3.hasNext()) {
                HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, resolver);
                    break;
                }
            }
        }

        return result;
    }

Looking at the above judgment, this design idea is still worth learning. First, obtain from the cache whether there is a corresponding type of parameter parser. If so, return directly. If not, obtain all parameter parsers for parsing.

RequestParamMethodArgumentResolver

Take a look at the analysis of the corresponding annotation:

    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                return true;
            } else {
                RequestParam requestParam = (RequestParam)parameter.getParameterAnnotation(RequestParam.class);
                return requestParam != null && StringUtils.hasText(requestParam.name());
            }
        } else if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        } else {
            parameter = parameter.nestedIfOptional();
            if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
                return true;
            } else {
                return this.useDefaultResolution ? BeanUtils.isSimpleProperty(parameter.getNestedParameterType()) : false;
            }
        }
    }

First judge whether it has RequestParam, and then judge whether it is of Map type, that is, whether the parameter data type is of Map data type (also supported)

Of course, Map type is not commonly used. What is written in the code above is RequestParam, and the parameter type is not Map type.

After you get the corresponding parameter parser, see how to parse it:

    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        } else {
            return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }
    }

Followed by:

    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();
        Object resolvedName = this.resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        } else {
            Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
            if (arg == null) {
                if (namedValueInfo.defaultValue != null) {
                    arg = this.resolveStringValue(namedValueInfo.defaultValue);
                } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                    this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
                }

                arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
            } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
                arg = this.resolveStringValue(namedValueInfo.defaultValue);
            }

            if (binderFactory != null) {
                WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

                try {
                    arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
                } catch (ConversionNotSupportedException var11) {
                    throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
                } catch (TypeMismatchException var12) {
                    throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
                }
            }

            this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
            return arg;
        }
    }

First resolve to the alias written to the method parameter in @ RequestParam, and then get the alias. Then cache the data carried in the request from the UrlPathHelper, and get the data from the cache in subsequent operations.

After getting the parameter name, start to determine the value of the parameter

    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
        Object arg;
        if (servletRequest != null) {
            arg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
            if (arg != MultipartResolutionDelegate.UNRESOLVABLE) {
                return arg;
            }
        }

        arg = null;
        MultipartRequest 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;
    }

You can see that the @ RequestParam annotation still supports MultipartRequest, that is, file upload. However, generally do not use @ RequestParam to upload files, but use @ RequestPart to upload files, because there is a problem with @ ReqeustParam annotation once in the project, and there is no problem with @ RequestPart to upload files.

Continue the above analysis and judge whether it is a file upload request after obtaining the parameters; If it is not a file upload request, you will go to:

        if (arg == null) {
            String[] paramValues = request.getParameterValues(name);
            if (paramValues != null) {
                arg = paramValues.length == 1 ? paramValues[0] : paramValues;
            }
        }

First, judge whether there are multiple parameters for this parameter? If there is only one, the first one is returned; If there are more than one, all values will be returned;

After parsing the corresponding value, you should first realize that the original value is obtained and the value needs to be converted;

Because the browser sends the string data type, but our parameters are of the corresponding integer type.

Converted code in this step:

 arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);

Get the binder to convert, that is, convert the requested parameters to the parameters on the method. Here you can get the data type on the method parameter or the passed string. Then you can find the corresponding parameter converter for conversion.

    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException {
        return this.getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
    }
    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
        ConversionFailedException conversionAttemptEx = null;
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                try {
                    return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                } catch (ConversionFailedException var14) {
                    conversionAttemptEx = var14;
                }
            }
        }

With the same design idea, first judge which conversion service can perform the conversion. If the conversion is supported, the corresponding conversion operation will be performed. Very understanding.

    public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToNumberConverterFactory.StringToNumber(targetType);
    }

Finally, I came to this class to convert the parameters of string type into Integer data type.

2,@RequestPart

Take a look at the class used for file upload

Corresponding operation code:

    @RequestMapping("upload")
    public String upload(@RequestPart("files")MultipartFile[] multipartFile){
        for (MultipartFile file : multipartFile) {
            String originalFilename = file.getOriginalFilename();
            File file1 = new File("C:\\Users\\lig\\Desktop\\tmp\\"+originalFilename);
            if (!file1.exists()){
                file1.mkdirs();
            }
            try {
                file.transferTo(file1);
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException("File upload failed");
            }
        }
        return "File uploaded successfully";
    }

Take a look at the corresponding parsing code:

    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");
        RequestPart requestPart = (RequestPart)parameter.getParameterAnnotation(RequestPart.class);
        boolean isRequired = (requestPart == null || requestPart.required()) && !parameter.isOptional();
        String name = this.getPartName(parameter, requestPart);
        parameter = parameter.nestedIfOptional();
        Object arg = null;
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);

Here, we will first judge whether it is a file upload request, and then if yes, we will directly take the corresponding file from the request:

    public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request) throws Exception {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest)WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
        boolean isMultipart = multipartRequest != null || isMultipartContent(request);
        // Is the parameter of MultipartFile type
        if (MultipartFile.class == parameter.getNestedParameterType()) {
            if (multipartRequest == null && isMultipart) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }

            return multipartRequest != null ? ((MultipartHttpServletRequest)multipartRequest).getFile(name) : null;
            // Is it list < multipartfile >
        } else if (isMultipartFileCollection(parameter)) {
            if (multipartRequest == null && isMultipart) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }
		   // Take out the file of the corresponding value
            return multipartRequest != null ? ((MultipartHttpServletRequest)multipartRequest).getFiles(name) : null;
            // Is it of type MultipartFile []
        } else if (isMultipartFileArray(parameter)) {
            if (multipartRequest == null && isMultipart) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }
			
            if (multipartRequest != null) {
                List<MultipartFile> multipartFiles = ((MultipartHttpServletRequest)multipartRequest).getFiles(name);
                return multipartFiles.toArray(new MultipartFile[0]);
            } else {
                return null;
            }
            // Is it part type, list < part >, part []
        } else if (Part.class == parameter.getNestedParameterType()) {
            return isMultipart ? request.getPart(name) : null;
        } else if (isPartCollection(parameter)) {
            return isMultipart ? resolvePartList(request, name) : null;
        } else if (isPartArray(parameter)) {
            return isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null;
        } else {
            return UNRESOLVABLE;
        }
    }

3,@RequestBody

{
    "id":11,
    "name":"liguang",
    "money":220
}

Back end:

    @RequestMapping("requestbody")
    public String reqeustBody(@RequestBody Dog dog){
        System.out.println(dog);
        return "receive requestbody Success of data type";
    }

The key code is as follows:

            if (isUnicode) {
                return this.objectMapper.readValue(inputMessage.getBody(), javaType);
            } else {
                Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
                return this.objectMapper.readValue(reader, javaType);
            }

Here is how to use jacson, which is very simple.

Keywords: Spring MVC

Added by sneakyimp on Sat, 22 Jan 2022 23:40:31 +0200