org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representatio

不知道你有没有碰到上面这个错误。本文会根据工作中碰到的该异常,梳理异常产生的原因。知道了原因,那么解决的办法也就很简单了。

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方法是关键。。

 

未完待续。。。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值