问题背景:调用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. Linux 一键安装最新内核并开启 BBR 脚本

    原文链接   https://teddysun.com/489.html 请到原文链接仔细阅读后操作.建议查看过脚本内容后操作,方便理解运行过程. 使用root用户登录,运行以下命令: wget -- ...

  2. 转 : Java的版本特性与历史

    Java Versions, Features and History This article gives you a highlight of important features added i ...

  3. JavaWeb -- Struts2 模型驱动

    1. 模型驱动 示例: 注册表单reg.jsp <%@ page language="java" contentType="text/html; charset=u ...

  4. 阿里云 centOS7.4新装nginx 不能访问

    反复装了几遍ngxin,什么防火墙,nginx.conf改了好几次都不能访问外网的ip, 原因是阿里云这货新的服务器根本就没开通443,80端口,真是坑人啊 点击配置规则,增加端口就行了 添加安全规则 ...

  5. java中int i 会出现i+1i吗

    Java中int是32,范围是-2147483648到2147483647 所以i+1 < i 或者 i-1 > i是会出现的. int i=(int) Math.pow(2, 32); ...

  6. 【spark】RDD创建

    首先我们要建立 sparkconf 配置文件,然后通过配置文件来建立sparkcontext. import org.apache.spark._ object MyRdd { def main(ar ...

  7. layui中实现上传图片压缩

    一.关于js上传图片压缩的方法,百度有很多种方法,这里我参考修改了一下: function photoCompress(file, w, objDiv) { var ready = new FileR ...

  8. c++指针初探

    业余时间准备重温一下c++,因为在阅读Android源码到native层的时候感觉有点吃力,只是在大学时候很不用心的学过c++,所以重温下以便打好一些编程基础知识,本篇就很简单的对c++的指针做初步的 ...

  9. hihocoder-1483区间价值 (二分+尺取法)

    题目链接: 区间价值 给定n个数A1...An,小Ho想了解AL..AR中有多少对元素值相同.小Ho把这个数目定义为区间[L,R]的价值,用v[L,R]表示. 例如1 1 1 2 2这五个数所组成的区 ...

  10. vim自动打开跳到上次的光标位置

    只需要vimrc里面加一个稍微复杂一点的autocmd就搞定了: if has("autocmd") au BufReadPost * && line(" ...