Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MapMethodProcessor should only resolve arguments of type Map or the ModelMap hierarchy #33160

Closed
effad opened this issue Jul 8, 2024 · 5 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@effad
Copy link

effad commented Jul 8, 2024

Affects: 6.1.6


This is a slight variation of SPR-17340 (#21874) that I've run into.

I have a Payload class that derives from Map<String, Object>:

public class Payload extends HashMap<String, Object> {
// some convenience methods here
}

And a method argument resolver for it:

public class PayloadMethodArgumentResolver extends RequestResponseBodyMethodProcessor {
...
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.getParameterType().equals(Payload.class);
	}
...
	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		Payload payload = new Payload();
...
		return payload;
	}
}

I want to use it like this:

	@PostMapping("/host")
	public ResponseEntity<?> createHost(Payload values) {
...
	}

Note that there is no annotation before Payload (it should not be necessary, since we have a custom type with a custom argument resolver. But, since there is no way to add my resolver to the top of the resolver list (org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.getDefaultArgumentResolvers()), the MapMethodProcessor will be used, returning an empty BindingAwareModelMap. Since this does not match the required type of createHost, an IllegalArgumentException will be thrown in org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(Object...).

I think the problem lies in org.springframework.web.method.annotation.MapMethodProcessor.supportsParameter(MethodParameter), which should not claim support for every subtype of Map (as it currently does). If the type check would be flipped like this, I think it should work correctly:

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.getParameterType().isAssignableFrom(Map.class) &&
				parameter.getParameterAnnotations().length == 0);
	}

Only if the type of the parameter can be assigned a Map should the MapMethodProcessor be applied.

Related Issues

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 8, 2024
@sbrannen sbrannen added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Jul 8, 2024
@sbrannen sbrannen changed the title MapMethodArgumentResolver precludes custom argument resolver when using derived type MapMethodArgumentResolver precludes custom argument resolver when using Map subtype Jul 8, 2024
@sbrannen sbrannen self-assigned this Jul 8, 2024
@sbrannen
Copy link
Member

sbrannen commented Jul 8, 2024

Hi @effad,

Congratulations on submitting your first issue for the Spring Framework! 👍

I think the problem lies in org.springframework.web.method.annotation.MapMethodProcessor.supportsParameter(MethodParameter), which should not claim support for every subtype of Map (as it currently does).

That seems like a reasonable assumption, and we'll investigate that approach.

If the type check would be flipped like this, I think it should work correctly:

Doing that alone unfortunately breaks existing support for ModelMap as a controller method argument.

Thus, if we "flip the type check" in MapMethodProcessor, we'll need to add back support for ModelMap -- for example, in either MapMethodProcessor or ModelMethodProcessor.

If we decide to make such changes, we'll also want to make sure that use cases like your Payload are supported in both Web MVC and WebFlux.

@sbrannen
Copy link
Member

sbrannen commented Jul 8, 2024

The following change in MapMethodProcessor is one possible solution for the above (for Web MVC).

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return ((parameter.getParameterType().isAssignableFrom(Map.class) ||
				ModelMap.class.isAssignableFrom(parameter.getParameterType())) &&
			parameter.getParameterAnnotations().length == 0);
}

@sbrannen sbrannen removed their assignment Jul 8, 2024
@sbrannen sbrannen added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jul 8, 2024
@sbrannen sbrannen added this to the 6.1.x milestone Jul 8, 2024
@effad
Copy link
Author

effad commented Jul 8, 2024

FWIW I have worked around the problem for the moment, by putting my own PayloadMethodArgumentResolver to the top of the list by overriding afterPropertiesSet in RequestMappingHandlerAdapter, like this:

	@Override
	protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
		return new RequestMappingHandlerAdapter() {
			@Override
			public void afterPropertiesSet() {
				super.afterPropertiesSet();
				List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(getArgumentResolvers());
				resolvers.add(0, payloadMethodArgumentResolver());
				setArgumentResolvers(resolvers);
			}
		};
	}

This feels quite hacky, though :-).

@rstoyanchev
Copy link
Contributor

@sbrannen I think this would be fine #33160 (comment). The goal is to be able to inject Map or ModelMap. Not sure there is a good reason to inject anything more specific, and is not guaranteed to work.

@sbrannen sbrannen self-assigned this Jul 9, 2024
@sbrannen sbrannen modified the milestones: 6.1.x, 6.1.11, 6.1.12 Jul 9, 2024
@sbrannen sbrannen modified the milestones: 6.1.12, 6.1.x, 6.1.13 Aug 13, 2024
@sbrannen sbrannen modified the milestones: 6.1.13, 6.1.14 Sep 11, 2024
@sbrannen sbrannen changed the title MapMethodArgumentResolver precludes custom argument resolver when using Map subtype MapMethodProcessor precludes custom argument resolver when using Map subtype Sep 13, 2024
@jhoeller jhoeller modified the milestones: 6.1.14, 6.2.x Oct 14, 2024
@jhoeller
Copy link
Contributor

Moving this to 6.2.x since we are not likely to resolve it for this week anymore, and there is a significant risk for side effects.

@sbrannen sbrannen removed their assignment Oct 29, 2024
@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed type: bug A general bug labels Nov 19, 2024
@rstoyanchev rstoyanchev modified the milestones: 6.2.x, 6.2.1 Nov 19, 2024
@rstoyanchev rstoyanchev changed the title MapMethodProcessor precludes custom argument resolver when using Map subtype MapMethodProcessor should resolve only Map or subtypes of ModelMap only Nov 19, 2024
@rstoyanchev rstoyanchev changed the title MapMethodProcessor should resolve only Map or subtypes of ModelMap only MapMethodProcessor should only resolve arguments of type Map or the ModelMap hierarchy Nov 19, 2024
@rstoyanchev rstoyanchev self-assigned this Dec 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants