Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
前端form表單資料提交時,後端出現 Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]
這樣的提示,也沒有觸發Controller的響應方法。
A: 第一種解決辦法
ajax請求的時候,把Content-Type,設定為 application/json; charset=utf-8
A: 第二種解決辦法
自定義引數解析器:
- 首先定義註解
@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
方法是這樣實現的:
@Override public boolean supportsParameter(MethodParameter parameter) { return Map.class.isAssignableFrom(parameter.getParameterType()); } 複製程式碼
這就導致了,在呼叫獲取方法引數的方法時(即呼叫 InvocableHandlerMethod.java
中的 getMethodArgumentValues
時)直接使用了 MapMethodProcessor
引數解析器(處理器),而自定義的引數解析器並沒有被呼叫。
翻了一下 HandlerMethodArgumentResolverComposite.java
的原始碼,引數解析器的選取是順序遍歷的,原始碼如下:
/** * 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; } 複製程式碼
好吧,怪不得輪不到自定義的引數解析器來處理。既然如此,那我改變一下自定義引數解析器的載入順序吧————渣渣我要騎到你們頭上去!
@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); } } 複製程式碼
好了,如此,完美的解決了上述問題。
這個Bug就聊到這兒吧。
