问题背景:调用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. LongestValidParentheses, 求最长合法括号子串长度-----同类问题ValidParentheses,GenerateParentheses

    问题描述:求括号字符串中最长合法子串长度.例如:()((),返回2,而不是4. 算法分析:还是利用栈,和判断合法括号对是一样的. public static int longestValidParen ...

  2. mssqlserver,mysql,oracle分页查询

    分页查询语句是sql语句编程中很长见的一个典型应用,用sql语句来分页比一些分页控件的速度要快,所以sql语句的分页在实际编程应用中还是非常广泛的. 今天给大家分享几条不同数据库编程用的分页查询语句. ...

  3. 修改Tomcat默认端口号,避免与IDEA冲突

    修改Tomcat默认端口号,避免与IDEA冲突 APT安装默认位置如下 /var/lib/tomcat8/conf 修改server.xml中的8080端口为8088或其他. 重启服务,试试看效果. ...

  4. NumPy切片和索引

    NumPy - 切片和索引 ndarray对象的内容可以通过索引或切片来访问和修改,就像 Python 的内置容器对象一样. 如前所述,ndarray对象中的元素遵循基于零的索引. 有三种可用的索引方 ...

  5. 机器学习三剑客之Pandas

      pandas Pandas是基于Numpy开发出的,专门用于数据分析的开源Python库 Pandas的两大核心数据结构 Series(一维数据)   Series   创建Series的方法   ...

  6. 使用springmvc的时候报错NoSuchBeanDefinitionException: No qualifying bean of type

    NoSuchBeanDefinitionException: No qualifying bean of type 其实我至今都不知道错误的根源在哪里,<context:component-sc ...

  7. 新东方雅思词汇---8.2、chron

    新东方雅思词汇---8.2.chron 一.总结 一句话总结:时间 chronic 英 ['krɒnɪk]  美 ['krɑnɪk]  adj. 慢性的:长期的:习惯性的 n. (Chronic)人名 ...

  8. 不合法的请求字符,不能包含\uxxxx格式的字符

    不合法的请求字符,不能包含\uxxxx格式的字符 ,不支持数组转json_encode()格式, 需要直接将数组,拼合成 json格式 ** * 微信api不支持中文转义的json结构 * @para ...

  9. 20165332 学习基础和C语言基础调查

    学习基础和c语言基础调查 一.技能学习经验 从小学过很多东西,架子鼓.电子琴.街舞.吉他.书法.美术......爱好也有很多,乒乓球.篮球.唱歌......这么多项技能,要说那一项比大多数人好,还真的 ...

  10. 51nod1241 lis变形

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1241 1241 特殊的排序 题目来源: 摩根斯坦利的比赛题 基准时间限制 ...