Springboot1.5与Springboot2.0中WebMvcAutoConfiguration不同实现导致Could not find acceptable

1.问题现象

想必经常在使用springmvc过程中,在接口路径中添加一些参数

 

但是当参数值包含了文件的后缀名时,例如xxx.mp4,xxx.jpg等,在springboot2.0版本中使用正常,但是在springboot1.5中则会抛出 Could not find acceptable representation 异常,没有找到正确的响应类型.

2.异常原因分析

无奈只好一步步断点进入查找原因,最终发现异常原因是springmvc会自动根据URL最后的一段参数解析响应媒体类型,解析出的响应类型与实际的响应类型不匹配,最后没有适合的MediaType响应类型则抛出异常,先给大家查下结果

 

最后解析为video/mp4类型

 

部分源码分析如下:

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
      implements HandlerMethodReturnValueHandler {
          protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
      ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
   
   HttpServletRequest request = inputMessage.getServletRequest();
   //获取适合的响应MediaType
   List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
   List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

   if (outputValue != null && producibleMediaTypes.isEmpty()) {
      throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
   }

   Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
   for (MediaType requestedType : requestedMediaTypes) {
      for (MediaType producibleType : producibleMediaTypes) {
          //MediaType进行匹配
         if (requestedType.isCompatibleWith(producibleType)) {
            compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
         }
      }
   }
   //此处没有匹配到合适的MediaType则抛出异常
   if (compatibleMediaTypes.isEmpty()) {
      if (outputValue != null) {
         throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
      }
      return;
   }
}
}

关键方法是获取适合的MediaType,getAcceptableMediaTypes,源码比较简单,关键代码如下:

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

strategies 是解析策略,项目启动时会初始化,而解析URL后缀名来获取mediaType的策略是 ServletPathExtensionContentNegotiationStrategy类,而这个是啥时候初始化进去的呢,分析发现是在创建ContentNegotiationManagerFactoryBean类时初始化的.

ContentNegotiationManagerFactoryBean类继承了FactoryBean并实现来 InitializingBean,获取bean对象时完成初始化,关键源码如下:

@Override
public void afterPropertiesSet() {
   List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();

   //favorPathExtension为true则添加
   if (this.favorPathExtension) {
      PathExtensionContentNegotiationStrategy strategy;
      if (this.servletContext != null && !isUseJafTurnedOff()) {
          //添加解析URI后缀名策略
         strategy = new ServletPathExtensionContentNegotiationStrategy(
               this.servletContext, this.mediaTypes);
      }
      else {
         strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
      }
      strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
      if (this.useJaf != null) {
         strategy.setUseJaf(this.useJaf);
      }
      strategies.add(strategy);
   }
   this.contentNegotiationManager = new ContentNegotiationManager(strategies);
}

favorPathExtension默认值为true

 

springboot1.5默认添加了ServletPathExtensionContentNegotiationStrategy 进行解析.而在springboot2.0中此值被org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#configureContentNegotiation(springboot1.5和springboot2,0实现不一样,这里就不再深入研究了) 设置为了false

 

解析到这里,异常原因大概已经清楚了,这里只对比了springboot1.5.4和springboot2.2.2版本.

3.解决方法

3.1 方式一

从上面分析中发现,URI解析的方法是 UriUtils.extractFileExtension(path),故可以在URI最后中多添加 '/' ,则可以不让这方法获取到后缀名.例如 http://127.0.0.1:8080/videoMerge/xxxxxx.mp4/

这种方式虽然可以解决,但是请求路径中多添加 / 感觉还是特别别扭.

3.2 方式二

从上面分析可以发现,Springboot2.0将favorPathExtension设置为了false不添加ServletPathExtensionContentNegotiationStrategy 策略,故我们也可以添加全局配置,不根据URI后缀名解析响应MediaType

@Configuration
public class ContentNegotiationPathConfig  extends WebMvcConfigurerAdapter {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        //不解析接口URL后缀名来解析mediaTye
        configurer.favorPathExtension(false);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值