前端form表单数据提交时,后端出现Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]
这样的提示,也没有触发Controller的响应方法。前端
ajax请求的时候,把Content-Type,设置为application/json; charset=utf-8
java
自定义参数解析器:web
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface P {
// ...
}
复制代码
public class Form2PojoArgumentResolver implements HandlerMethodArgumentResolver
{
@Override
public boolean supportsParameter(MethodParameter parameter)
{
return parameter.hasParameterAnnotation(P.class);
}
@Nullable
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception
{
// 返回对应的参数类型的数据
}
}
复制代码
@Configuration
public class ArgumentResolversConfig implements WebMvcConfigurer
{
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
{
resolvers.add(new Form2PojoArgumentResolver());
}
}
复制代码
public class TestController
{
@RequestMapping("xxx")
public testArgumentResolver(@P TestEntity entity)
{
// ...
}
}
复制代码
一般状况下,前端form表单的数据到Controller中时就转换成的对象。可是我这里出现了特殊情形。 因为个人TestEntity实现了Map接口,而Spring Boot在启动时会自动加载一系列的解析器,而这些解析其中有一个叫MapMethodProcessor
的处理器,它的supportsParameter
方法是这样实现的:ajax
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Map.class.isAssignableFrom(parameter.getParameterType());
}
复制代码
这就致使了,在调用获取方法参数的方法时(即调用InvocableHandlerMethod.java
中的getMethodArgumentValues
时)直接使用了MapMethodProcessor
参数解析器(处理器),而自定义的参数解析器并无被调用。spring
翻了一下HandlerMethodArgumentResolverComposite.java
的源码,参数解析器的选取是顺序遍历的,源码以下:json
/**
* Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
*/
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
// 遍历参数解析器,寻找符合要求的
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
if (logger.isTraceEnabled()) {
logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
parameter.getGenericParameterType() + "]");
}
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
复制代码
又翻了一下RequestMappingHandlerAdapter.java
的源码,参数解析器的加载顺序是这个样子的:后端
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
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());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
复制代码
好吧,怪不得轮不到自定义的参数解析器来处理。既然如此,那我改变一下自定义参数解析器的加载顺序吧————渣渣我要骑到大家头上去!bash
@Configuration
public class ArgumentResolversConfig
{
@Autowired
private RequestMappingHandlerAdapter adapter;
@PostConstruct
public void injectSelfMethodArgumentResolver()
{
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
argumentResolvers.add(new Form2PojoArgumentResolver());
argumentResolvers.addAll(adapter.getArgumentResolvers());
adapter.setArgumentResolvers(argumentResolvers);
}
}
复制代码
好了,如此,完美的解决了上述问题。app
这个Bug就聊到这儿吧。 ide