问题背景:调用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. java中规范语句

    1. 直接常量:A=a,a是数字,是定死的数字,简单说是常数 符号常量:A=a,a是定死的符号,

  2. 关于jQuery中的offset()和position()

    在jQuery中有两个获取元素位置的方法offset()和position().position()方法是在1.2.6版本之后加入的,为什么要引 入这个方法呢?这两个方法之间有什么异同?使用的时候应该 ...

  3. srs部署到ubuntu 18.04 server

    srs.txt ubuntu 18.04 安装 srs 1. 上传srs_40.7z和h2ws.7z到linux服务器,然后远程ssh连接 (假设登陆用户名是bob,linux服务器ip是192.16 ...

  4. RegularExpressions(正则表达式)

    最近在不少地方用到了正则表达式,一直对这一块不太熟悉,今天写一些关于正则表达式的知识,一来是总结自己学的知识,二来今后忘记了可以及时的复习. 在java中想应用正则表达式带来的好处,必须先了解两个类, ...

  5. poj3181 背包+大数

    http://poj.org/problem?id=3181 Dollar Dayz Time Limit: 1000MS   Memory Limit: 65536K Total Submissio ...

  6. VMware虚拟机克隆Linux系统引起的网卡问题

    1. 手动配置静态网卡地址不生效2. 网卡名变成了eth1[root@localhost network-scripts]# ls |grep ifcfg ifcfg-eth0 ifcfg-lo [r ...

  7. 014——VUE中v-if语法在网站注册中的实际应用

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. ARM汇编指令集1

    (汇编)指令是CPU机器指令的助记符,经过编译过会得到一串0011组成的机器码,可以由CPU读取执行. (汇编)伪指令本质不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译 ...

  9. 条款16:成对使用new以及delete的时候应该采取相同的形式

    首先思考下面的代码: ]; ... delete stringArray; 这是最常见的错误之一了,new与delete不配对,但是仔细想想new与delete为什么一定要配对呢? 可以想到一种可能就 ...

  10. 条款10:让operator=返回一个reference to *this

    例如对象x,y,z.要实现连锁赋值(假设operator=已经重载过了):x = y = z,那么operator=则必须返回一个*this. 注意这个条款不仅仅适合于operator=,对于oper ...