不知道你有没有碰到上面这个错误。本文会根据工作中碰到的该异常,梳理异常产生的原因。知道了原因,那么解决的办法也就很简单了。
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:227) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:284) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:231) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:56) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:297) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1085) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1070) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:891) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:851) ~[spring-webmvc-3.2.13.RELEASE.jar:3.2.13.RELEASE]
先看异常堆栈。根据异常堆栈可以看出,大概流程是http请求首先到达前端控制器DisPatcherServlet,然后调用HandlerMapping,查找匹配的Handler。而异常就出现在查找匹配的Handler这里。
我们今天就顺着源码,找一下问题的根本原因。
先看最终抛异常的handleNoMatch方法:
/**
* Iterate all RequestMappingInfos once again, look if any match by URL at
* least and raise exceptions accordingly.
* @throws HttpRequestMethodNotSupportedException if there are matches by URL
* but not by HTTP method
* @throws HttpMediaTypeNotAcceptableException if there are matches by URL
* but not by consumable/producible media types
*/
@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
String lookupPath, HttpServletRequest request) throws ServletException
从注释可以看出,该方法会遍历入参requestMappingInfos,查找和lookupPath匹配的requestMappingInfo。并且,当和lookupPath匹配,但consumable或producible不匹配时,会抛出 HttpMediaTypeNotAcceptableException 异常。
那么,requestMappingInfos、lookupPath 和 consumable/producible分别是什么呢?我这里先给出结论,后面我们再从源码层面解释:lookupPath就是我们请求的url,requestMappingInfos是我们通过@Controller注解或在xml文件中定义的http接口。看一下具体的逻辑
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
String lookupPath, HttpServletRequest request) throws ServletException {
Set<String> allowedMethods = new LinkedHashSet<String>(4);
Set<RequestMappingInfo> patternMatches = new HashSet<RequestMappingInfo>();
Set<RequestMappingInfo> patternAndMethodMatches = new HashSet<RequestMappingInfo>();
for (RequestMappingInfo info : requestMappingInfos) {
if (info.getPatternsCondition().getMatchingCondition(request) != null) {
patternMatches.add(info);
if (info.getMethodsCondition().getMatchingCondition(request) != null) {
patternAndMethodMatches.add(info);
}
else {
for (RequestMethod method : info.getMethodsCondition().getMethods()) {
allowedMethods.add(method.name());
}
}
}
}
if (patternMatches.isEmpty()) {
return null;
}
else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
}
Set<MediaType> consumableMediaTypes;
Set<MediaType> producibleMediaTypes;
Set<String> paramConditions;
if (patternAndMethodMatches.isEmpty()) {
consumableMediaTypes = getConsumableMediaTypes(request, patternMatches);
producibleMediaTypes = getProducibleMediaTypes(request, patternMatches);
paramConditions = getRequestParams(request, patternMatches);
}
else {
consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches);
producibleMediaTypes = getProducibleMediaTypes(request, patternAndMethodMatches);
paramConditions = getRequestParams(request, patternAndMethodMatches);
}
if (!consumableMediaTypes.isEmpty()) {
MediaType contentType = null;
if (StringUtils.hasLength(request.getContentType())) {
try {
contentType = MediaType.parseMediaType(request.getContentType());
}
catch (IllegalArgumentException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
}
throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes));
}
else if (!producibleMediaTypes.isEmpty()) {
throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes));
}
else if (!CollectionUtils.isEmpty(paramConditions)) {
String[] params = paramConditions.toArray(new String[paramConditions.size()]);
throw new UnsatisfiedServletRequestParameterException(params, request.getParameterMap());
}
else {
return null;
}
}
上面贴出了方法的源码,我们挑出相关的重点看。
for (RequestMappingInfo info : requestMappingInfos) {
if (info.getPatternsCondition().getMatchingCondition(request) != null) {
patternMatches.add(info);
if (info.getMethodsCondition().getMatchingCondition(request) != null) {
patternAndMethodMatches.add(info);
}
else {
for (RequestMethod method : info.getMethodsCondition().getMethods()) {
allowedMethods.add(method.name());
}
}
}
}
首先遍历 requestMappingInfos,for循环中,有两层if判断。外层if,根据请求的url,找出匹配的 requestMapping;内存if,则进一步从匹配url的mapping中找出参数也匹配的。url匹配的放入patternMatches中,而url、方法都匹配的,放入 patternAndMethodMatches 中。
再往下,会根据 patternAndMethodMatches 是否为空,决定调用 getProducibleMediaTypes 方法的入参。由于该方法返回的producibleMediaTypes 不为空,导致最终抛出异常。
private Set<MediaType> getProducibleMediaTypes(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
Set<MediaType> result = new HashSet<MediaType>();
for (RequestMappingInfo partialMatch : partialMatches) {
if (partialMatch.getProducesCondition().getMatchingCondition(request) == null) {
result.addAll(partialMatch.getProducesCondition().getProducibleMediaTypes());
}
}
return result;
}
而上述方法返回非空,是由于patternAndMethodMatches 中有RequestMappingInfo满足了下面的条件:
partialMatch.getProducesCondition().getMatchingCondition(request) == null
看一下getMatchingCondition方法:
/**
* Checks if any of the contained media type expressions match the given
* request 'Content-Type' header and returns an instance that is guaranteed
* to contain matching expressions only. The match is performed via
* {@link MediaType#isCompatibleWith(MediaType)}.
* @param request the current request
* @return the same instance if there are no expressions;
* or a new condition with matching expressions;
* or {@code null} if no expressions match.
*/
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
return this;
}
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>(expressions);
for (Iterator<ProduceMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ProduceMediaTypeExpression expression = iterator.next();
if (!expression.match(request)) {
iterator.remove();
}
}
return (result.isEmpty()) ? null : new ProducesRequestCondition(result, this.contentNegotiationManager);
}
match方法是关键。。
未完待续。。。