support for a parameter with an index in the query String when bound to @RequestParam

  Kiến thức lập trình

Many http clients pass an array to queryString in this form:

?attributes[]=field1&attributes[]=field2

I’m trying to get Controller to work with this format through RequestParam:

@RestController
public class FooController {
   
   @RequestMapping(method = RequestMethod.GET,
    value = "/foo",
    produces = "application/json")
   ResponseEntity<FooDto> getFoo(@RequestParam(value = "attributes", required = false) List<String> attributes) {
   }
}

However, when executing a request, null is passed to the controller method as the attributes value.

I found that the “RequestParamMethodArgumentResolver.resolveName” method receives “attributes” as the value of the parameter name, while this parameter is stored in the request object using the key “attributes[]”. Therefore, the argument of the controller method is null, because resolver cannot find a match.

My questions:

  1. maybe spring mvc has support for this format “attributes[]=field1&attributes[]=field2” when using @RequestParam? WebDataBinder#adaptEmptyArrayIndices support it
  2. I tried to write an extension for RequestParamMethodArgumentResolver. Is it correct to place such logic in the resolver itself?
@Component
public class SpringMvcConfig implements WebMvcRegistrations {

  @Override
  public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
    return new CustomRequestMappingHandlerAdapter();
  }

  public static class CustomRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {

    @Override
    public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
      log.info("setArgumentResolvers");
      var list1 = new ArrayList<>(argumentResolvers);
      OptionalInt index = IntStream.range(0, list1.size())
          .filter(i -> {
            var resolver = list1.get(i);
            return !(resolver instanceof RequestParamMethodArgumentResolver
                     && !(resolver instanceof CustomRequestParamMethodArgumentResolver));
          })
          .findFirst();
      var customResolver = new CustomRequestParamMethodArgumentResolver(getBeanFactory(), false);
      list1.set(index.orElse(0), customResolver);
      super.setArgumentResolvers(list1);
    }
  }

  @Slf4j
  public static class CustomRequestParamMethodArgumentResolver extends RequestParamMethodArgumentResolver {

    public CustomRequestParamMethodArgumentResolver(boolean useDefaultResolution) {
      super(useDefaultResolution);
    }

    public CustomRequestParamMethodArgumentResolver(
        ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
      super(beanFactory, useDefaultResolution);
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
      return super.supportsParameter(parameter);
    }

    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
      if (
          (List.class.isAssignableFrom(parameter.getParameterType()) ||
          Set.class.isAssignableFrom(parameter.getParameterType()))
          && (request.getParameterMap().containsKey(name + "[]"))) {
          return super.resolveName(name + "[]", parameter, request);

      }
      return super.resolveName(name, parameter, request);
    }
  }
}

LEAVE A COMMENT