Radu
Radu

Reputation: 2060

Combine functionality of @ModelAttribute and @RequestBody in Spring MVC

Consider a @PostMapping in Spring MVC, and we want to map the request body to a DTO, as well as other request parameters like query and path variables.

For mapping the request body we can use the @RequestBody annotation on a parameter, which will tell Spring to use the RequestResponseBodyMethodProcessor.

For mapping the request parameters we can use the @ModelAttribute annotation (or avoid any annotations; same effect), which will tell Spring to use the ServletModelAttributeMethodProcessor.

But is there an easy way to combine these two? Is there a way to make spring first map the DTO with request parameters and then override with data deserialized from the JSON in the body?

The only way I see it at the moment is to create a custom HandlerMethodArgumentResolver but I'd like to reuse any existing Spring functionality first.

Upvotes: 4

Views: 805

Answers (1)

Radu
Radu

Reputation: 2060

Turns out no, not at time of writing anyway.

RequestMappingHandlerAdapter.getDefaultArgumentResolvers holds the default configuration for the argument resovlers, and we have:

  • RequestResponseBodyMethodProcessor handling the @RequestBody annotation, and
  • ServletModelAttributeMethodProcessor handling the @ModelAttribute, or any parameter without any annotations.

It's always just one single resolver that gets executed, even if you try using a composite.

My solution

@ControllerAdvice
@RequiredArgsConstructor
@Order(HIGHEST_PRECEDENCE)
public class ServletRequestBinderRequestBodyAdvice extends RequestBodyAdviceAdapter {

    private final ServletRequest servletRequest;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        copyDefaultPropertiesThatWhereOverwritenWithNull(parameter, body);

        new ExtendedServletRequestDataBinder(body).bind(servletRequest);

        return body;
    }

    private void copyDefaultPropertiesThatWhereOverwritenWithNull(MethodParameter parameter, Object arg) {
        Object argWithDefaults = instantiateClass(parameter.getParameterType());
        copyPropertiesSkippingNulls(argWithDefaults, arg);
    }

}

Upvotes: 1

Related Questions