解决SpringMvc返回json报Response 406 Not Acceptable

今天通过spring mvc3.2.0 中@ResponseBody对ajax请求进行返回json数据的时候,后台提示:

ExceptionHandlerExceptionResolver - Resolving exception from handler [......]org.springframework.web.HttpMediaTypeNotAcceptableException : Could not find acceptable representation

firebug显示:

162651_zJCV_723271.png

162745_EGaC_723271.png


浏览器向Controller中传递的是参数:aaaaa@aaa.com.au会报这种异常;  但是如果是参数aaaaa@aaa.com则不会报异常。

想着问题肯定出在‘au’上,au这个玩意儿很像一种媒体类型,暂时定位到spring接收参数后进行json数据格式包装是出错了,包装的时候搞成其他类型(非json)。于是debug跟踪spring mvc源码:

根据问题定位,跟踪到这里:


163137_PeeK_723271.png


AbstractMessageConverterMethodProcessor类的writeWithMessageConverters方法:   

           

  /**
             * Writes the given return value to the given web request. Delegates to
             * {@link #writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)}
             */
             protected <T> void writeWithMessageConverters(T returnValue,MethodParameter returnType,NativeWebRequest webRequest)
                                     throws IOException, HttpMediaTypeNotAcceptableException {
                        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
                        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
                        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
            }



接着调用本类的writeWithMessageConverters方法:        

protected <T> void writeWithMessageConverters (T returnValue,MethodParameter returnType,ServletServerHttpRequest inputMessage,ServletServerHttpResponse outputMessage)
                                     throws IOException, HttpMediaTypeNotAcceptableException {
                        Class<?> returnValueClass = returnValue.getClass();
                        HttpServletRequest servletRequest = inputMessage.getServletRequest();
                        List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
                        List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
                        Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
                         for (MediaType r : requestedMediaTypes) {
                                     for (MediaType p : producibleMediaTypes) {
                                                 if (r.isCompatibleWith(p)) {
                                                            compatibleMediaTypes.add(getMostSpecificMediaType(r, p));
                                                }
                                    }
                        }
                         if (compatibleMediaTypes.isEmpty()) {
                                    throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes );
                        }
                         ......
                        }
                         throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes );
            }


 跟踪getAcceptableMediaTypes方法:

private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
        List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
        return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes;
    }


调用ContentNegotiationManager类的resolveMediaTypes方法获得mediatype:

public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
                         for (ContentNegotiationStrategy strategy : this. contentNegotiationStrategies) {
                                    List<MediaType> mediaTypes = strategy.resolveMediaTypes(webRequest);
                                     if (!mediaTypes.isEmpty()) {
                                                 return mediaTypes;
                                    }
                        }
                         return Collections. emptyList();
            }


而接口ContentNegotiationStrategy有很多实现,明显的策略模式,要拿到这个mediaTypes有几个策略:

164243_hhpg_723271.png


然而根据相应策略到了类AbstractMappingContentNegotiationStrategy的resolveMediaTypes方法:           

public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) {
                        String key = getMediaTypeKey(webRequest);
                         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();
            }



重点来了,首先是根据webRequest获取key,key是啥呢?跟踪到 getMediaTypeKey(webRequest)方法内:

跟踪到类MappingMediaTypeFileExtensionResolver的getMediaTypeKey方法:

      

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




原来key是获取参数值的扩展名,也就是.au.获取来了做什么呢?

之前 resolveMediaTypes方法中的lookupMediaType,handleNoMatch方法,根据key(au)去找spring中自带的媒体类型。

不幸的是au正是其中的媒体类型:

164401_YFu8_723271.png

 拿到了mediaType,我们回到之前AbstractMessageConverterMethodProcessor类的writeWithMessageConverters方法:

164516_r23w_723271.png

其中requestedMediaTypes的类型是[audio/basic],producibleMediaTypes的类型是[application/json;charset=UTF-8, application/*+json;charset=UTF-8, application/json;charset=UTF-8, application/*+json;charset=UTF-8],两个类型不符,固然进入:

 if (compatibleMediaTypes.isEmpty()) {
     throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes );
 }

抛出了异常。

那我们怎么解决呢?


spring 提供了这么一个类:org.springframework.web.accept.ContentNegotiationManagerFactoryBean。

帮助我们选择自己想要的 media types策略。

/**
 * A factory providing convenient access to a {@code ContentNegotiationManager}
 * configured with one or more {@link ContentNegotiationStrategy} instances.
 *
 * <p>By default strategies for checking the extension of the request path and
 * the {@code Accept} header are registered. The path extension check will perform
 * lookups through the {@link ServletContext} and the Java Activation Framework
 * (if present) unless {@linkplain #setMediaTypes(Properties) media types} are configured.
 *
 * @author Rossen Stoyanchev
 * @since 3.2
 */
public class ContentNegotiationManagerFactoryBean
                         implements FactoryBean<ContentNegotiationManager>, InitializingBean, ServletContextAware {
             private boolean favorPathExtension = true;
             private boolean favorParameter = false;
             private boolean ignoreAcceptHeader= false;
             private Properties mediaTypes = new Properties();
             private Boolean useJaf;
             private String parameterName;
             private MediaType defaultContentType;
             private ContentNegotiationManager contentNegotiationManager;
             private ServletContext servletContext;
......



我们暂且看看这个类的属性就知道他是做什么的了。

favorPathExtension 是否根据request path的扩展名进行选择mediaType。

favorParameter 是否根据参数选择medieType。

ignoreAcceptHeader 是否忽略浏览器传过来的acceptHeader。

mediaTypes 媒体类型。

那么顺其自然,我们在spring mvc配置文件中servlet-context.xml中这样配置即可解决:

<mvc:annotation-driven  content-negotiation-manager="contentNegotiationManager"/>
 < beans:bean id= "contentNegotiationManager" class= "org.springframework.web.accept.ContentNegotiationManagerFactoryBean" >  
    <beans:property name ="favorPathExtension" value= "false" />  
    <beans:property name ="favorParameter" value="false" />  
    <beans:property name ="ignoreAcceptHeader" value= "false" />   
    <beans:property name ="mediaTypes" >  
        <beans:value > 
            atom=application/atom+ xml 
            html=text/html  
            json=application/json  
            *=*/* 
        </beans:value >  
    </beans:property > 
</beans:bean>


这样直接根据http的acceptHeader的策略进行选择mediaTypes。问题解决。

另外在解决问题的过程中,看到说这是spring mvc 3.2.0的一个bug,可以将版本降到3.1即可解决。

还有一种解决方法:参考http://stackoverflow.com/questions/14333709/spring-mvc-3-2-and-json-objectmapper-issue。

这种.au的参数让我碰到了这种问题,也是人品啊。


转载于:https://my.oschina.net/stefanzhlg/blog/365579

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值