Handler Method ArgumentResolver: Controller Method Input into Automatic Packager

Keywords: Java Spring Attribute less

Every sentence

Your work is efficient and your boss will think you are not strong enough. You have many bug s in your code and fire fighting in various production environments. The boss will think you are the core member of the team.

Preface

When enjoying the convenience of Spring MVC, have you ever wondered whether the handler method parameters of Controller can be automatically encapsulated (sometimes even without @PathVariable,@RequestParam,@RequestBody annotations), or even write HttpServletRequest, HttpSession, Writer at any location of the method parameters? ... and other types of parameters, it automatically has value can be directly used.
Do you want to ask: How does Spring MVC work? Then this article will unveil its mystery and return you a piece of "innocence".

Spring MVC, as one of the most popular web frameworks, has long been a standardization framework in practical sense. Especially with the sudden collapse of Struts 2, Spring MVC almost rides the dust, so it has far-reaching significance to understand it in depth.

Spring MVC only needs a few annotations to make a common java method a Handler processor. It also has a series of powerful functions such as automatic parameter encapsulation, return value view processing/rendering, which makes coder more focused on its own business.

web frameworks like JSF, Google Web Toolkit, Grails Framework, etc., at least I haven't used them.
Here's a lightweight web framework: Play Framework design I personally find interesting and interesting to play with.

HandlerMethodArgumentResolver

Policy interface: Used to parse method parameters into parameter values in the context of a given request. It handles all the parameters in your Handler method: automatic encapsulation, automatic assignment, validation and so on. With it, Spring MVC processing becomes so advanced and automated.
Spring MVC has a lot of implementations built in. Of course, if you can't meet your needs, you can still customize and register yourself. I'll give you some examples of customization later.

There is an image formula: Handler Method Argument Resolver = Handler Method + Argument (parameter) + Resolver (parser).
Interpretation: It is a parser of HandlerMethod method, which parses HttpServletRequest (content in header + body) into parameters of HandlerMethod method.

//@ Parametric parser in since 3.1 HandlerMethod method
public interface HandlerMethodArgumentResolver {

    // Determine whether Handler Method ArgumentResolver supports Method Parameter
    // PS: Usually through the annotation above the parameter | parameter type)
    boolean supportsParameter(MethodParameter parameter);
    
    // Getting data from Native Web Request, Model AndViewContainer is used to provide access to Model
    // MethodParameter parameter: Request parameter
    // WebDataBinderFactory is used to create a WebDataBinder for data binding, validation
    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

Processor implementation classes based on this interface are not abundant, very many. My screenshot is as follows:

Because there are many subclasses, so I classify them for illustration. I describe it in four categories:

  1. Name-based
  2. The data type is Map's
  3. Fixed parameter type
  4. Message Converter Based on ContentType

Category 1: Name-based

Get values by name key from URI (path variable), HttpServletRequest, HttpSession, Header, Cookie, etc.

All of these processors are based on the abstract class AbstractNamedValueMethod ArgumentResolver, which is the most important branch (classification).

// @ since 3.1 is responsible for getting values from path variables, requests, first-class. (You can specify properties such as name, required, default value, etc.)
// Subclasses need to do the following: get naming information for method parameters, and resolve names to parameter values
// When parameter values are needed, missing parameter values are handled and analytical values are optionally handled.

//Notice in particular that the default value can use the ${} placeholder, or that the SpEL statement #{} is problematic.
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Nullable
    private final ConfigurableBeanFactory configurableBeanFactory;
    @Nullable
    private final BeanExpressionContext expressionContext;
    private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);

    public AbstractNamedValueMethodArgumentResolver() {
        this.configurableBeanFactory = null;
        this.expressionContext = null;
    }
    public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
        this.configurableBeanFactory = beanFactory;
        // The default is RequestScope
        this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, new RequestScope()) : null);
    }

    // protected's inner class, so all subclasses (annotations) are friends of three attribute values
    protected static class NamedValueInfo {
        private final String name;
        private final boolean required;
        @Nullable
        private final String defaultValue;
        public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
            this.name = name;
            this.required = required;
            this.defaultValue = defaultValue;
        }
    }

    // The core method notes that this method is final and does not want subclasses to override it.~
    @Override
    @Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        // Create NamedValueInfo corresponding to MethodParameter
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        // Support for java.util.Optional supported in Java 8
        MethodParameter nestedParameter = parameter.nestedIfOptional();

        // The name attribute (that is, the value/name attribute of the annotation) resolves both placeholders and SpEL expressions, which is very powerful.
        // Because the name at this point may still be wrapped in the ${} symbol, it is parsed through the Bean Expression Resolver.
        Object resolvedName = resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }


        // Template abstraction: Resolve the given parameter type and value name into parameter value. Implemented by subclasses
        // @ PathVariable - > decodedUriVariables value obtained by uri parsing (commonly used)
        // @ RequestParam - > obtained by HttpServletRequest.getParameterValues(name) (commonly used)
        // @ RequestAttribute - > Obtained by HttpServletRequest.getAttribute(name) < -- Here scope is request
        // @ Session Attribute - > Brief
        // @ Request Header - > obtained by HttpServletRequest.getHeaderValues(name)
        // @ CookieValue - > obtained by HttpServletRequest.getCookies()
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);

        // If the parsed value is still null, go to defaultValue (if specified)
        if (arg == null) {
            // You can see that defaultValue also supports placeholders and SpEL~~~
            if (namedValueInfo.defaultValue != null) {
                arg = resolveStringValue(namedValueInfo.defaultValue);

            // If Arg == null & & defaultValue == null & & non-optional parameters are handled by handleMissingValue, they are generally reported as exceptions.
            } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                
                // It is a protected method that throws a ServletRequestBindingException exception by default
                // Each subclass overrides this method and instead throws its own exception (but all are exception subclasses of ServletRequestBindingException)
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
    
            // handleNullValue is a private method to handle null values
            // For Bool type, there is this judgment: Boolean.TYPE.equals(paramType) returns Boolean. FALSE;
            // Note here: Boolean.TYPE = Class.getPrimitiveClass("boolean") refers to the basic type of boolean, not the Boolean type.~~~
            // If you reach this point (value is null), but you are still the basic type, then throw an exception (only boolean type will not throw an exception ~)
            // Let's say that even if the request pass value is & bool = 1, the effect is the same as that of bool=true (1: true 0: false) and case-insensitive (TrUe effect is the same as true).
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }
        // Compatible with empty strings, if the incoming string is empty, still use the default values (default values support placeholders and SpEL)
        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }

        // Complete automated data binding~~~
        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
                // Converter converter in data binder converts arg to a specified type of value
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            } catch (ConversionNotSupportedException ex) { // Note this exception: Method ArgumentConversion NotSupported Exception type mismatch exception
                throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            } catch (TypeMismatchException ex) { //MethodArgumentTypeMismatchException is a subclass of TypeMismatchException
                throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());

            }
        }

        // protected's method, which is an empty implementation, is given to subclasses to copy (not necessary)
        // Only Path Variable Method ArgumentResolver stores the value of parsing to 
        // HttpServletRequest.setAttribute (if key already exists, it will not be stored)
        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
    }


    // Here is the cache, which records that each MethodParameter object value is a NamedValueInfo value
    private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
        NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
        if (namedValueInfo == null) {
            // createNamedValueInfo is an abstract method, and subclasses must be implemented
            namedValueInfo = createNamedValueInfo(parameter);
            // updateNamedValueInfo: This step is what we said earlier about why Spring MVC can be encapsulated according to parameter names
            // If info.name.isEmpty() is not specified in the annotation, the parameter name is obtained by `parameter.getParameterName()'.~
            // It also handles defaultValue: ` n t t ... and so on specified in the annotation, which are treated as null.
            // After all: new NamedValueInfo(name, info.required, defaultValue); (equivalent to parsing the annotation into this object ~)
            namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
            this.namedValueInfoCache.put(parameter, namedValueInfo);
        }
        return namedValueInfo;
    }

    // Abstract method 
    protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
    // Subclasses take out values by name
    protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception;
}

The main logic (template logic) of parsing parameters is defined in the abstract class, and the subclass only needs to implement the corresponding abstract template method.
I will outline the steps to deal with this part as follows:

  1. Construction of NameValueInfo <--mainly name, defaultValue, required based on MethodParameter.
  2. Resolve name by BeanExpressionResolver(${} placeholder and SpEL)
  3. Through template method resolveName obtains the corresponding attribute values from HttpServletRequest, Http Headers, URI template variables, etc. (specifically implemented by subclasses)
  4. In the case of arg==null, either the default value is used, and if required = true & arg==null, an exception is generally reported (except boolean type ~)
  5. Converting arg to Methodparameter.getParameterType() through WebDataBinder (Note: This is just a data conversion, not a bind() method)

The abstract class inheritance tree is as follows:

As can be seen from the above source code, abstract classes have been fixed to deal with templates (the method is final), leaving little to do for subclasses, and there are generally three things as follows:

  1. Create NameValueInfo based on MethodParameter (the implementation of subclasses can be inherited from NameValueInfo, which is the attributes of corresponding annotations)
  2. Get attribute values from HttpServletRequest, Http Headers, URI template variables, and so on, depending on the method parameter name
  3. Handling the case of Arg = null (not required)

PathVariableMethodArgumentResolver

It helps Spring MVC implement restful-style URLs. It is used to process method parameters annotated with @PathVariable annotations and to get values from URLs (not? The following parameters).
And, moreover, it can also parse a Map whose value value value of the @PathVariable annotation is not empty (less used, less personally recommended)~

UriComponents Contributor Interface: To build a policy interface for UriComponents by looking at method parameters and parameter values and deciding which part of the target URL should be updated.

// @ since 4.0 is still relatively late
public interface UriComponentsContributor {

    // This method is exactly the same as that of Handler Method ArgumentResolver.~~~
    boolean supportsParameter(MethodParameter parameter);
    // Processing a given method parameter, then updating UriComponentsbuilder, or adding uri variables to the map to extend uri after processing all the parameters~~~
    void contributeMethodArgument(MethodParameter parameter, Object value, UriComponentsBuilder builder,
            Map<String, Object> uriVariables, ConversionService conversionService);
}

Its three implementation classes are:

Regarding the use of this interface, we will focus on it later. It is suggested that automatic selective neglect be adopted here.

// @ Note that since 3.0 only supports methods (processors) tagged with @RequestMapping~
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
    @AliasFor("name")
    String value() default "";
    @AliasFor("value")
    String name() default "";
    
    // Note: It doesn't have defaultValue.~

    // @ since 4.3.3 It is also marked as false not required~~~~
    boolean required() default true;
}

// @since 3.1
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver implements UriComponentsContributor {
    private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);


    // In a simple sentence: @PathVariable is a must, no matter what type you are.
    // Annotations are annotated and are of Map type.
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (!parameter.hasParameterAnnotation(PathVariable.class)) {
            return false;
        }
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
            return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
        }
        return true;
    }

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
        return new PathVariableNamedValueInfo(ann);
    }
    private static class PathVariableNamedValueInfo extends NamedValueInfo {
        public PathVariableNamedValueInfo(PathVariable annotation) {
            // DEFAULT_NONE for default values~~~
            super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE);
        }
    }

    // The process of getting values according to name is very simple, but it is related to the previous knowledge only.
    // As for when the attr was put in, AbstractHandlerMethodMapping.handleMatch() matches the processor method
    // The parameters are extracted through UrlPathHelper.decodePathVariables(), and stored temporarily on the request attribute.~~~
    // For Handler Mapping content, here's https://blog.csdn.net/f641385712/article/details/89810020
    @Override
    @Nullable
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
        return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
    }

    // MissingPathVariableException is a subclass of ServletRequestBindingException
    @Override
    protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
        throw new MissingPathVariableException(name, parameter);
    }


    // After the value is completely processed, the processed value is put into the request domain for easy use when rendering in view.~
    // The handleResolvedValue method of the abstract parent class, which only overrides it~
    @Override
    @SuppressWarnings("unchecked")
    protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {

        String key = View.PATH_VARIABLES;
        int scope = RequestAttributes.SCOPE_REQUEST;
        Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
        if (pathVars == null) {
            pathVars = new HashMap<>();
            request.setAttribute(key, pathVars, scope);
        }
        pathVars.put(name, arg);
    }
    ...
}

For the use of @PathVariable, no more examples are needed.
The only thing that needs to be said is that if the type is Map type, the use of the precautions are as follows:

@PathVariable("jsonStr") Map<String,Object> map

We want to parse the string corresponding to jsonStr into key-value pairs and encapsulate them in Map. So you have to, you have to, you have to register Converter / Property Editor (custom) that can handle this string. It is relatively troublesome to use, but it has high technology concealment. I don't usually recommend this.~

Notes on required=false of @PathVariable

This function is questioned by many people, how to use it???

@ResponseBody
@GetMapping("/test/{id}")
public Person test(@PathVariable(required = false) Integer id) { ... }

I think it can be accessed through the url / test. In fact, this is not possible. It will be 404.
Correct posture:

@ResponseBody
@GetMapping({"/test/{id}", "/test"})
public Person test(@PathVariable(required = false) Integer id) { ... }

In this way / test and / test/1 URLs will work properly~

@ PathVariable requires = false is rarely used, usually when multiple values are passed through a URL, but some values are used when they are not required. For example, such URLs as "/ user/{id}/{name}", "user/{id}" and "user"

RequestParamMethodArgumentResolver

As the name implies, it parses the method input parser labeled @RequestParam, which is much more powerful than the above annotation, which is used to get the value from the request parameter (?) to complete the encapsulation. This is the vast majority of our usage scenarios. In addition, it supports MultipartFile, which means it can get data from MultipartHttpServletRequest | HttpServletRequest, and it also handles "simple types" without any annotations.~

// @since 2.5
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";
     // @since 4.2
    @AliasFor("value")
    String name() default "";
    boolean required() default true;
    String defaultValue() default ValueConstants.DEFAULT_NONE;
}
// @since 3.1
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver implements UriComponentsContributor {

    private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);

    // This parameter is always important:
    // true: Represents that the parameter type is the basic type reference BeanUtils# is SimpleProperty (what Enum, Number, Date, URL, wrapper type, array type of the above types, etc.)
    // If it's a basic type, even if you don't write the @RequestParam annotation, it's going to come in and process ~ (this @PathVariable doesn't).
    // fasle: Except above. If you want it to be processed, you have to annotate it, such as List, etc.~
    // The default value is false
    private final boolean useDefaultResolution;

    // This construct only `MvcUriComponentsBuilder'calls the incoming false
    public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
        this.useDefaultResolution = useDefaultResolution;
    }
    // Configurable BeanFactory is passed in, so it supports processing placeholders ${...} and SpEL.
    // This construct is called in RequestMapping Handler Adapter, and eventually true is passed in to Catch-all Case, which is an interesting design.
    public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
        super(beanFactory);
        this.useDefaultResolution = useDefaultResolution;
    }

    // This processor can handle the following cases:
    // 1. All types of @RequestParam annotations (non-Map)/annotations specify the Map type of value value value (converter provided by oneself)
    // ====== The following all indicate that @RequestParam is not annotated=======
    // 1. Cannot be annotated with @RequestPart annotation, otherwise it will not be processed directly.
    // 2. Upload request: is MultipartArgument () = true (MultipartFile type or corresponding set/array type or javax.servlet.http.Part corresponding combination/array type)
    // 3. In the case of useDefaultResolution=true, the "basic type" is also handled
    @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;
            }
        }
    }


    // It can also be seen from this that even with the @RequestParam annotation, a NamedValueInfo can be created.
    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
        return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
    }


    // Core method: Get values based on Name (plain / file upload)
    // And there are collections, arrays, etc.
    @Override
    @Nullable
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

        // This parses out a MultipartFile or its collection/array
        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 the parsed value is still null, then after processing the file upload, you can go to the parameter to get it.
        // It can be seen that the priority of file upload is higher than the request parameters.
        if (arg == null) {
        
            //Little knowledge: getParameter() is essentially the effect of getParameterNames()[0]
            // Emphasize once: the result of? ids=1,2,3 is ["1,2,3"] (compatible mode, not recommended. Note: Only comma-separated)
            // The result of? IDS = 1 & IDs = 2 & IDs = 3 is [1, 2, 3] (standard way of passing values, recommended)
            // But Spring MVC can receive both with List. Be sure to pay attention to their differences.~~~
            String[] paramValues = request.getParameterValues(name);
            if (paramValues != null) {
                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
            }
        }
        return arg;
    }
    ...
}

You can see that the ArgumentResolver processor is still very powerful: it can not only handle parameters labeled @RequestParam, but also receive file upload parameters. Even packages that you don't normally use without the annotation are done with it. As for how it works, see the following Sao operation:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
    ...
    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        ...
        // Catch-all
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));

        return resolvers;
    }
    ...
}

You can see that the Servlet Model Attribute Method Processor, like the Request Param Method Argument Resolver, has a bottomless effect.

At the end of this article, I collected some doubts in the process of using them to solve them. I hope it can also help you to be open-minded.

How do get requests pass arrays, collections (List s)

This case is so common that we often encounter the need to pass values to the back end using get requests (such as batch queries based on ids). But in the end how to pass, how to write the URL, should be silly and unclear uncertainty.

@ Path Variable Reference
    @ResponseBody
    @GetMapping("/test/{objects}")
    public Object test(@PathVariable List<Object> objects) {
        System.out.println(objects);
        return objects;
    }

Request URL: /test/fsx,fsx,fsx. Console Printing:

[fsx, fsx, fsx]

Collection reception was successful (using @PathVariable Object[] objects is also acceptable).
The following two points should be paid attention to when using:

  1. Multiple values can only be used, separated by numbers (otherwise they will be treated as a value and put into arrays/collections without error).
  2. @ PathVariable annotations are required. Otherwise, it will be handled by the Servlet Model Attribute Method Processor, which requires a free construct, so the reflection creation instance will report an error (array/List). (Note: If you write ArrayList < Object > objects in this way, it will not be wrong, but the value must not be encapsulated, just an empty object)

Explanation: Why comma-separated String types can be converted into arrays and collections by default. Refer to StringToCollectionConverter/StringToArray Converter, a built-in GenericConverter Universal Converter.~~

@ RequestParam Reference
    @ResponseBody
    @GetMapping("/test")
    public Object test(@RequestParam List<Object> objects) {
        System.out.println(objects);
        return objects;
    }

Request URL: / test/?objects=1,2,3. Console Printing:

[1, 2, 3]

Change the request URL to: / test/? Objects = 1 & objects = 2 & objects = 3. Console Printing:

[1, 2, 3]

The URLs of the two requests are different, but they all work correctly. (@RequestParam Object[] objects can also wrap two URLs as well)

You have to pay attention to the following details: For the collection List, the @RequestParam annotation must exist, otherwise the error will be as follows (because it's handled in the back pocket):

But if you write String[] objects like this, you can wrap them properly even without annotations.

Note: Object[] objects written in this way can't be without commentary (error reporting as above). As for the reason, you can think for yourselves and leave me a message if you don't want to understand it.~

PS: It should be noted that so many Handler Method Argument Resolvers in Spring MVC are parsed in sequence: if multiple Handler Method Argument Resolvers can parse a type, parse it first in order (the latter will not be parsed again).

Source code reference: Handler Method ArgumentResolver Composite. getArgumentResolver (Method Parameter parameter);

Since RequestParamMethod ArgumentResolver can also parse Multipart file uploads, and the default order is before RequestPart Method ArgumentResolver, parameters of Multipart type will be parsed by RequestParamMethod ArgumentResolver without the @RequestPart annotation.

summary

This is a very important article for you to understand the powerful automatic data encapsulation function of Spring MVC. It introduces the functions and basic usage of Handler Method ArgumentResolver, as well as the two most important annotations @PathVariable and @RequestParam and their respective ArgumentResolver processors.
Because of the huge system, I will describe it in several chapters. Welcome to subscribe and keep on paying attention to it.~

Relevant Reading

Handler Mapping Source Details (2) - - Request Mapping Handler Mapping Series

Handler Method ArgumentResolver (1): Controller method into parameter auto-encapsulator (parse parameter into value) [Enjoy learning Spring MVC]
Handler Method ArgumentResolver (II): Map parameter type and fixed parameter type [Enjoy Spring MVC]
Handler Method ArgumentResolver (3): Parametric Processor Based on HttpMessageConverter Message Converter

Knowledge exchange

== The last: If you think this is helpful to you, you might as well give a compliment. Of course, sharing your circle of friends so that more small partners can see it is also authorized by the author himself.~==

** If you are interested in technical content, you can join the wx group: Java Senior Engineer and Architect.
If the group two-dimensional code fails, Please add wx number: fsx641385712 (or scan the wx two-dimensional code below). And note: "java into the group" will be manually invited to join the group**
== If you are interested in Spring, Spring Boot, MyBatis and other source code analysis, you can add me wx: fsx641385712, invite you to join the group and fly together manually.==

Posted by spivey on Wed, 28 Aug 2019 01:08:45 -0700