Home > Enterprise >  Spring ArgumentResolver to get pathvariable map and
Spring ArgumentResolver to get pathvariable map and

Time:10-14

this my method signature

@RequestMapping(value = {"/article", "/article/{id}", "/article/{name}"}, method = RequestMethod.GET,
                consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
 
  public ResponseEntity<JsonNode> get(@PathVariable Map<String, String> pathVarsMap, @RequestParam(value="test") MultiValueMap<String, String> test, @RequestBody(required=false) JsonNode requestBody )

I want to make this into

public ResponseEntity<JsonNode> get( MyStructure mystr)

where MyStructure will have @PathVariable Map<String, String> pathVarsMap, @RequestParam(value="test") MultiValueMap<String, String> test, @RequestBody(required=false) JsonNode requestBody inside of it.

I know that I have to use custom resolvers and implement resolveArgument. One of the examples i saw did (Map<String, String>) httpServletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE). But im not sure how to get it to work. Can i create MultiValueMap and RequestBody inside MyString ?

In another place, I see that the recommendation is to use

@Nonnull
protected final Map<String, String>   getUriTemplateVariables(NativeWebRequest request) {
@SuppressWarnings("unchecked")
Map<String, String> variables =
        (Map<String, String>) request.getAttribute(
                URI_TEMPLATE_VARIABLES_ATTRIBUTE, SCOPE_REQUEST);
return (variables != null) ? variables : Collections.<String, String>emptyMap();
}

so im a bit confused on how should i be implementing this

CodePudding user response:

All @PathVariable , @RequestParam and @RequestBody can only be annotated on the method parameters , so there are no ways for you to annotate them on the object fields.

The codes of the existing HandlerMethodArgumentResolver that resolve the values for these annotations also assume these annotation are annotated on the method parameters ,that means you also cannot simply delegate to them to resolve the value for your request object.

Your best bet is to simply reference the corresponding HandlerMethodArgumentResolver for each annotation and copy the related codes to your implementation.

  • For @PathVariable , it is resolved by PathVariableMapMethodArgumentResolver
  • For @RequestParam on MultiValueMap , it is resolved by RequestParamMapMethodArgumentResolver
  • For @RequestBody , it is resolved by RequestResponseBodyMethodProcessor . Internally , it works with a list of HttpMessageConverter to read the HTTP request body. As you are now using Jackson to read the request body , you only need to focus on MappingJackson2HttpMessageConverter for simplicity.

It is easier than I expected. There following implementation should be a good starting point for you.

First define MyStructure class :

public class MyStructure {
    public Map<String, String> pathVariables;
    public MultiValueMap<String, String> queryParameters;
    public JsonNode requestBody;
}

And implement MyStructureArgumentResolver :

public class MyStructureArgumentResolver implements HandlerMethodArgumentResolver {

        private MappingJackson2HttpMessageConverter messageConverter;

        public MyStructureArgumentResolver(MappingJackson2HttpMessageConverter messageConverter) {
            super();
            this.messageConverter = messageConverter;
        }

        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return MyStructure.class.isAssignableFrom(parameter.getParameterType());
        }

        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

            MyStructure request = new MyStructure();

            request.queryParameters = resolveQueryParameters(webRequest);
            request.pathVariables = resolvePathVariables(webRequest);
            request.requestBody = resolveRequestBody(webRequest, parameter);

            return request;
        }

        private MultiValueMap<String, String> resolveQueryParameters(NativeWebRequest webRequest) {
            // resolve all query parameter into MultiValueMap
            Map<String, String[]> parameterMap = webRequest.getParameterMap();
            MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
            parameterMap.forEach((key, values) -> {
                for (String value : values) {
                    result.add(key, value);
                }
            });
            return result;
        }

        private Map<String, String> resolvePathVariables(NativeWebRequest webRequest) {
            Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(
                    HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);

            if (!CollectionUtils.isEmpty(uriTemplateVars)) {
                return new LinkedHashMap<>(uriTemplateVars);
            } else {
                return Collections.emptyMap();
            }
        }

        private JsonNode resolveRequestBody(NativeWebRequest webRequest, MethodParameter parameter)
                throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);

            MediaType contentType;
            try {
                contentType = inputMessage.getHeaders().getContentType();
            } catch (InvalidMediaTypeException ex) {
                throw new HttpMediaTypeNotSupportedException(ex.getMessage());
            }
            if (contentType == null) {
                contentType = MediaType.APPLICATION_OCTET_STREAM;
            }
            Class<?> contextClass = parameter.getContainingClass();

            JsonNode body = JsonNodeFactory.instance.objectNode();

            if (messageConverter.canRead(JsonNode.class, contextClass, contentType)) {
                body = (JsonNode) messageConverter.read(JsonNode.class, inputMessage);
            }

            return body;
        }
    }

Then register MyStructureArgumentResolver :

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private MappingJackson2HttpMessageConverter messageConverter;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new MyStructureArgumentResolver(messageConverter));
    }
}

And use it in the controller method :

@RequestMapping(value = { "/test/{name}" }, method = RequestMethod.GET)
public ResponseEntity<String> test(MyStructure request) {

}

CodePudding user response:

@PostMapping("/get")
public ResponseEntity<JsonNode> get( @RequestBody MyStructure mystr){...}

When call this api, fill in params in request body, send body as application/json. Refer to this sample: sample project

  • Related