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

Spring MVC custom argument resolvers order #23043

Closed
victorkevin2 opened this issue May 28, 2019 · 6 comments
Closed

Spring MVC custom argument resolvers order #23043

victorkevin2 opened this issue May 28, 2019 · 6 comments
Labels
status: feedback-provided Feedback has been provided

Comments

@victorkevin2
Copy link

victorkevin2 commented May 28, 2019

When I want to custom one argument Resolver in :
WebMvcConfigurerAdapter#addArgumentResolvers,and try to override parameter type of ServletRequest ; but I found it that caught low priority after ServletRequestMethodArgumentResolver (same parameter type of ServletRequest) ;
review source RequestMappingHandlerAdapter#getDefaultArgumentResolvers discover below :

        // Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

I think that custom arguments resolver should be before the default argument ,so that can get highly priority to handle my logic;
Thanks .

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label May 28, 2019
@victorkevin2 victorkevin2 changed the title Spring-mvc custom Argument Resolvers Spring-mvc custom Argument Resolvers order May 28, 2019
@bclozel
Copy link
Member

bclozel commented May 28, 2019

The Javadoc is addressing that bit:

Custom argument resolvers are invoked before built-in resolvers except for those that rely on the presence of annotations (e.g. @RequestParameter, @PathVariable, etc). The latter can be customized by configuring the RequestMappingHandlerAdapter directly.

I don't know if we should update the doc or slightly change the registration order. Before doing any of that, I'd like to know what type of argument resolver you're registering and to what types it's supposed to react to. This would help us figure out how specific it is and if it should be done via the configurer or the handler adapter directly.

Could you provide some background? (what are you trying to achieve, a code snippet showing your custom resolver). Thanks!

@bclozel bclozel added the status: waiting-for-feedback We need additional information before we can continue label May 28, 2019
@victorkevin2
Copy link
Author

victorkevin2 commented May 28, 2019

I have a subclass of javax.servlet.http.HttpServletRequestWrapper
( class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest ) ,see below :

public class YJJHttpRequest extends HttpRequestWrapper {

	public YJJHttpRequest(HttpServletRequest request) {
		super(request);
	}
	public KdUser getKdUser() throws ValidateTokenException {
		.....
		return kdUser;
	}

there is a HandlerMethodArgumentResolver Implementation class
( register it in WebMvcConfigurerAdapter#addArgumentResolvers ) target is to translate HttpServletRequest instance to YJJHttpRequest type like this :

public class HttpRequestHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver,{

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (AdminHttpRequest.class.isAssignableFrom(paramType) || 
             YJJHttpRequest .class.isAssignableFrom(paramType)
				|| CommonHttpRequest.class.isAssignableFrom(paramType));
	}

	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
			WebDataBinderFactory binderFactory) throws Exception {
		Class<?> paramType = parameter.getParameterType();
		HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
		if (AdminHttpRequest.class.isAssignableFrom(paramType)) {
			return new AdminHttpRequest(request);
		} else if (YJJHttpRequest.class.isAssignableFrom(paramType)) {
			return new YJJHttpRequest(request);
		} else {
			return new CommonHttpRequest(request);
		}
	}

finally I want to define this parameter type in Controller's method like this

@RequestMapping(value = "cancelFavorPriFund")

public void cancelFavorPriFund(YJJHttpRequest request, HttpResponseWrapper response)  {
		KdUser kdUser = request.getKdUser();
		JSONArray crmCodeArray = request.getJSONArrayQuietly("crmCode");
                .....
		response.outputSuccess();
}

In fact wil caught a exception :
java.lang.IllegalStateException: Current request is not of type [com.keynes.newlife.common.request.YJJHttpRequest ] org.springframework.mock.web.MockHttpServletRequest

I found that ServletRequestMethodArgumentResolver have a high priority to handle it ,and thus mine HttpRequestHandlerMethodArgumentResolver have no chance to executed ,so throwed this exception from ServletRequestMethodArgumentResolver ,because ServletRequestMethodArgumentResolver and HttpRequestHandlerMethodArgumentResolver supports the same Parameter Type .

in other words: when default Argument Resolvers and custom Argument Resolvers supports same parameter type , custom argument resolver always no chance to executed .
Thanks!

The Javadoc is addressing that bit:

Custom argument resolvers are invoked before built-in resolvers except for those that rely on the presence of annotations (e.g. @RequestParameter, @PathVariable, etc). The latter can be customized by configuring the RequestMappingHandlerAdapter directly.

I don't know if we should update the doc or slightly change the registration order. Before doing any of that, I'd like to know what type of argument resolver you're registering and to what types it's supposed to react to. This would help us figure out how specific it is and if it should be done via the configurer or the handler adapter directly.

Could you provide some background? (what are you trying to achieve, a code snippet showing your custom resolver). Thanks!

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels May 28, 2019
@rstoyanchev
Copy link
Contributor

rstoyanchev commented May 28, 2019

Similar request and discussion under #21874. Changing the order isn't an option but we can check explicitly that the request is either ServletRequest or HttpServletRequest (and MultipartRequest or MultipartHttpServletRequest). Any other sub-class we wouldn't know how to resolve.

@rstoyanchev rstoyanchev changed the title Spring-mvc custom Argument Resolvers order Spring MVC custom argument resolvers order May 28, 2019
@victorkevin2
Copy link
Author

In the end, I found an workaround. But I don't think it's the best

public class HttpRequestHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver, InitializingBean   {
                                                                                            
        @Autowired
	private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

	public void afterPropertiesSet() throws Exception {
		List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
		List<HandlerMethodArgumentResolver> newArgumentResolvers = new LinkedList<>();
		newArgumentResolvers.add(this);
		newArgumentResolvers.addAll(argumentResolvers);
 requestMappingHandlerAdapter.setArgumentResolvers(Collections.unmodifiableList(newArgumentResovers));                                                                                                                                                                                                                                                                          
	}

@rstoyanchev
Copy link
Contributor

Okay but just to be clear, the change I suggested in ServletRequestMethodArgumentResolver would have made your custom resolver work with the current order, because the built-in resolver would pass on any concrete request type it does not recognize.

@rstoyanchev rstoyanchev removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 8, 2021
@lleming
Copy link

lleming commented Jan 17, 2023

A little bit more info in which cases this might be usefull. I use the same techinque(upper sample, with RequestMappingHandlerAdapter) , only with HandlerMethodReturnValueHandler, and face same issue with ordering. There is an legacy application containing something similar to ResponseEntity, it represents payload or error, includes operation result status and it is sent via different transport (rpc call ?). At first I tried to use spring MessageWriter but it can't change http status only the way how to serialize entity. That is why I have to use HandlerMethodReturnValueHandler.
Also I don't like to do manual or copy/paste, just want to introduce mapper between my custom object and ResponseEntity. Spring already contains HttpEntityMethodProcessor to handle ResponseEntity type. I want to reuse it and I just need to replace it with my own, and apply mapping before returned value passed to original handler.

MockMvc will still suffer from same ordering problem even if initialized as

@Bean
MockMvc mockMvc(SampleController sampleController, CustomResponseReturnValueHandler customResponseReturnValueHandler) {
    return MockMvcBuilders.standaloneSetup(sampleController)
                    .setCustomReturnValueHandlers(customResponseReturnValueHandler)
                    .build(); 
}

I may miss something, it sounds like common problem but I didn't find builtin/existing solution. What could be done in such case is ability to change order or to intoduce customization of HttpEntityMethodProcessor instead of hardcoding processing of ResponseEntity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided
Projects
None yet
Development

No branches or pull requests

5 participants