问题背景:调用http的post接口返回一个String类型的字符串时中文出现乱码,定位出问题后在@RequestMapping里加produces注解produces = "application/json;charset=utf-8",再次请求http报406,代码发现spring抛出异常:org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation。

  问题代码附上:

/**
* 执行登陆行为
*
* @author wulinfeng
* @param request
* @param user
* @return
* @throws ServletException
* @throws IOException
*/
@RequestMapping(value = "/loginAction.html", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
public @ResponseBody String loginAction(HttpServletRequest request, HttpServletResponse response,
@RequestBody UserBean user)
throws ServletException, IOException
{
// 验证码校验
String validateCode = (String)request.getSession().getAttribute("randomString");
if (StringUtils.isEmpty(validateCode) || !validateCode.equals(user.getValidate().toUpperCase()))
{
return PropertiesConfigUtil.getProperty("verify_code_error");
} // 用户名密码校验
String result = testPillingService.login(user.getUsername(), user.getPassword()); // 校验通过,创建token并放入session中;校验失败,返回错误描述
if ("success".equals(result))
{
String tokenId = UUID.randomUUID().toString(); // 登陆成功后是使用cookie还是session来存放tokenId
if (IS_COOKIE.equals("1"))
{
Cookie cookie = new Cookie("tokenId", tokenId);
cookie.setMaxAge(3 * 24 * 60 * 60); // 3天过期
response.addCookie(cookie);
}
else
{
request.getSession(true).setAttribute("tokenId", tokenId);
} if (user.getUsername().toUpperCase().equals("ADMIN"))
{
return "register";
}
}
return result;
}

  问题定位:spring源码逆向跟踪,我们从异常抛出的地方回溯到问题发生的地方。

  异常所在地:RequestMappingInfoHandlerMapping类235行,标红;producibleMediaTypes实例化处,218行,标红

     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 (InvalidMediaTypeException 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)) {
throw new UnsatisfiedServletRequestParameterException(paramConditions, request.getParameterMap());
}
else {
return null;
}

  判断请求是否能匹配注解produces配置的Content-Type(即“application/json;charset=utf-8”):类258行

 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;
 }

  匹配逻辑:ProducesRequestCondition类185行

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);
}

  匹配请求的Content-Type:AbstractMediaTypeExpression类75行

public final boolean match(HttpServletRequest request) {
try {
boolean match = matchMediaType(request);
return (!this.isNegated ? match : !match);
}
catch (HttpMediaTypeException ex) {
return false;
}
}

  获取请求匹配的Content-Type:ProducesRequestCondition类300行、236行

protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
return true;
}
}
return false;
}
private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes;
}

  解析请求Content-Type:ContentNegotiationManager类109行

    public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException { for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
continue;
}
return mediaTypes;
}
return Collections.emptyList();
}

  好了,到底了,最终解析Content-Type的地方在这里,AbstractMappingContentNegotiationStrategy类

public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
throws HttpMediaTypeNotAcceptableException { return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
} /**
* An alternative to {@link #resolveMediaTypes(NativeWebRequest)} that accepts
* an already extracted key.
* @since 3.2.16
*/
public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, String key)
throws HttpMediaTypeNotAcceptableException { if (StringUtils.hasText(key)) {
MediaType mediaType = lookupMediaType(key);
if (mediaType != null) {
handleMatch(key, mediaType);
return Collections.singletonList(mediaType);
}
mediaType = handleNoMatch(webRequest, key);
if (mediaType != null) {
addMapping(key, mediaType);
return Collections.singletonList(mediaType);
}
}
return Collections.emptyList();
}

  怎么取到html这个后缀的呢?AbstractMappingContentNegotiationStrategy的子类PathExtensionContentNegotiationStrategy类114行

protected String getMediaTypeKey(NativeWebRequest webRequest) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
logger.warn("An HttpServletRequest is required to determine the media type key");
return null;
}
String path = this.urlPathHelper.getLookupPathForRequest(request);
String filename = WebUtils.extractFullFilenameFromUrlPath(path);
String extension = StringUtils.getFilenameExtension(filename);
return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null;
}

  回到最顶端,我的@RequestMapping匹配的url是“/loginAction.html”,getMediaTypeKey方法就是在取url后缀,拿到html后作为上面resolveMediaTypeKey方法的里key,然后去调用lookupMediaType方法

protected MediaType lookupMediaType(String extension) {
return this.mediaTypes.get(extension.toLowerCase(Locale.ENGLISH));
}

  而这里mediaTypes对象是什么东西呢?它是启动时加载的,我这里取出来是这样子的:{xml=application/xml, html=text/html, json=application/json},所以最终解析出来我的请求竟然是text/html,而实际上我从ajax调用http时是设置了Content-Type为application/json;charset=UTF-8的。

  看到这里,问题已经出来了,url以html结尾,导致请求头设置的Content-Type被覆盖了。那么解决方式相对就简单了,不以html结尾即可,我这里是直接把/loginAction.html改为/loginAction,重新试一下,406没有了,中文也出来了。

  

spring mvc加了@produces注解后报406的更多相关文章

  1. J2EE进阶(十三)Spring MVC常用的那些注解

    Spring MVC常用的那些注解 前言 Spring从2.5版本开始在编程中引入注解,用户可以使用@RequestMapping, @RequestParam,@ModelAttribute等等这样 ...

  2. Spring mvc 加载HTML静态页面

    看到网上大部分举例Spring MVC加载静态页面HTML方式都还要通过controller, 根据js和css文件的加载模式,html也同样可以直接加载 在spring的配置文件中例如 *-serv ...

  3. Hibernate Validation,Spring mvc 数据验证框架注解

    1.@NotNull:不能为 Null,但是可以为Empty:用在基本数据类型上. @NotNull(message="{state.notnull.valid}", groups ...

  4. Spring MVC 中的基于注解的 Controller【转】

    原文地址:http://my.oschina.net/abian/blog/128028 终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 H ...

  5. Spring MVC 中的基于注解的 Controller(转载)

           终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 HandlerMapping 来映射出相应的 handler 并调用相应的方法 ...

  6. Spring MVC工作原理 及注解说明

    SpringMVC框架介绍 1) spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面. Spring 框架提供了构建 Web 应用程序的全功 ...

  7. Spring MVC (二)注解式开发使用详解

    MVC注解式开发即处理器基于注解的类开发, 对于每一个定义的处理器, 无需在xml中注册. 只需在代码中通过对类与方法的注解, 即可完成注册. 定义处理器 @Controller: 当前类为处理器 @ ...

  8. Spring MVC(五)--控制器通过注解@RequestParam接受参数

    上一篇中提到,当前后端命名规则不一致时,需要通过注解@RequestParam接受参数,这个注解是作用在参数上.下面通过实例说明,场景如下: 在页面输入两个参数,控制器通过注解接受,并将接受到的数据渲 ...

  9. spring/spring boot/spring mvc中用到的注解

    在spring Boot中几乎可以完全弃用xml配置文件,本文的主题是分析常用的注解. Spring最开始是为了解决EJB等大型企业框架对应用程序的侵入性,因此大量依靠配置文件来“非侵入式”得给POJ ...

随机推荐

  1. Hypernetes简介——真正多租户的Kubernetes Distro

    http://www.dockone.io/article/838 Hypernetes是一个真正多租户的Kubernetes Distro. Hypernetes在Kubernetes基础上增加了多 ...

  2. QTabWidget和QtabBar的区别

    切换标签tab 是QTabBar int QTabWidget::insertTab ( int index, QWidget * page, const QString & label ) ...

  3. LeetCode第[33]题(Java):Search in Rotated Sorted Array

    题目:在翻转有序中搜索 难度:Medium 题目内容: Suppose an array sorted in ascending order is rotated at some pivot unkn ...

  4. HTML5 ——web audio API 音乐可视化(二)

    上一篇 web audio API 音乐可视化(一)介绍了一些基本的API,以及如何简单的播放一个音频,本篇介绍一下怎么对获取到的音频进行分析,并将分析后的数据绘制成图像. 最终效果请戳这里; 完整版 ...

  5. Netty原理

    1. Netty简介Netty是一个高性能.异步事件驱动的NIO框架,基于JAVA NIO提供的API实现.它提供了对TCP.UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都 ...

  6. Prism初研究之使用Prism实现WPF的MVVM模式

    转自:http://www.cnblogs.com/qianzi067/p/5804880.html

  7. HYSBZ - 2818莫比乌斯反演

    链接 题意很简洁不说了 题解:一开始我想直接暴力,复杂度是O(log(1e7)*sqrt(1e7))算出来是2e9,可能会复杂度爆炸,但是我看时限是10s,直接大力莽了一发暴力,没想到就过了= = 就 ...

  8. 三十三 Python分布式爬虫打造搜索引擎Scrapy精讲—数据收集(Stats Collection)

    Scrapy提供了方便的收集数据的机制.数据以key/value方式存储,值大多是计数值. 该机制叫做数据收集器(Stats Collector),可以通过 Crawler API 的属性 stats ...

  9. IOS-MapKit

    一.MapKit框架的使用 MapKit框架使用须知 MapKit框架中所有数据类型的前缀都是MK MapKit有一个比较重要的UI控件 :MKMapView,专门用于地图显示   跟踪显示用户的位置 ...

  10. 八大排序算法的java实现

    有时间再贴算法分析图 JDK7的Collections.sort()的算法是TimSort, 适应性的归并排序, 比较晦涩难懂, 这里没有实现 public class mySort { // 冒泡排 ...