Spring MVC遇到一个Unknown return value type XXX的错误,不少有经验的同窗可能会会说这简单在Controller的方法上加上ResponseBody注解就能够了。java
对于不少时候是有效的,由于这是咱们常见的错误,可是可是也有无效的时候,咱们这篇文章就来梳理一下为何会出现这个问题。web
最重要的是咱们将简单的梳理一下Spring MVC中的一些关键步骤,让你们对整个数据的流转有一个更加清晰的认识。spring
HandlerAdapter在Spring MVC中的重要性就没必要多说了,若是你时间很少,那么建议直接了解RequestMappingHandlerAdapter这一个HandlerAdapter就能够了,由于这是咱们使用的最多的,在高版本中若是没有配置默认使用的就是RequestMappingHandlerAdapter。json
对于RequestMappingHandlerAdapter咱们就奔着最多见的说,就是咱们使用最多的HttpMessageConverter。mvc
RequestMappingHandlerAdapter的HttpMessageConverter设置有3中方式:app
第2中应该是最多见的配置HttpMessageConverter的方式了。下面咱们看一下这些设置方式有什么不一样。ide
public RequestMappingHandlerAdapter() { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316 this.messageConverters = new ArrayList<>(4); this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(stringHttpMessageConverter); try { this.messageConverters.add(new SourceHttpMessageConverter<>()); } catch (Error err) { // Ignore when no TransformerFactory implementation is available } this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); }
没有配置RequestMappingHandlerAdapter,高版本默认使用的就是RequestMappingHandlerAdapter,在构造函数中添加了4个HttpMessageConverter。函数
private ManagedList<?> getMessageConverters(Element element, @Nullable Object source, ParserContext context) { Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters"); ManagedList<Object> messageConverters = new ManagedList<>(); if (convertersElement != null) { messageConverters.setSource(source); for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) { Object object = context.getDelegate().parsePropertySubElement(beanElement, null); messageConverters.add(object); } } if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) { messageConverters.setSource(source); messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source)); RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source); stringConverterDef.getPropertyValues().add("writeAcceptCharset", false); messageConverters.add(stringConverterDef); messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(ResourceRegionHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source)); if (romePresent) { messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source)); } if (jackson2XmlPresent) { Class<?> type = MappingJackson2XmlHttpMessageConverter.class; RootBeanDefinition jacksonConverterDef = createConverterDefinition(type, source); GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source); jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true); jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef); messageConverters.add(jacksonConverterDef); } else if (jaxb2Present) { messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source)); } if (jackson2Present) { Class<?> type = MappingJackson2HttpMessageConverter.class; RootBeanDefinition jacksonConverterDef = createConverterDefinition(type, source); GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source); jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef); messageConverters.add(jacksonConverterDef); } else if (gsonPresent) { messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source)); } if (jackson2SmilePresent) { Class<?> type = MappingJackson2SmileHttpMessageConverter.class; RootBeanDefinition jacksonConverterDef = createConverterDefinition(type, source); GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source); jacksonFactoryDef.getPropertyValues().add("factory", new SmileFactory()); jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef); messageConverters.add(jacksonConverterDef); } if (jackson2CborPresent) { Class<?> type = MappingJackson2CborHttpMessageConverter.class; RootBeanDefinition jacksonConverterDef = createConverterDefinition(type, source); GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source); jacksonFactoryDef.getPropertyValues().add("factory", new CBORFactory()); jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef); messageConverters.add(jacksonConverterDef); } } return messageConverters; }
如上所示,是经过xml中配置了message-converters,使用AnnotationDrivenBeanDefinitionParser解析,会添加的HTTPMessageConvert。this
register-defaults设置为true的前提下,若是依赖中有jackson就会注册MappingJackson2HttpMessageConverter,若是依赖中有gson就会注册GsonHttpMessageConverter。spa
[message-converters配置]
如上是message-converters配置,在运行中咱们能够看到实际上加上默认添加的HttpMessageConverter的全部可用HttpMessageConverter以下所示。
[可用HttpMessageConverter]
handleInternal方法对MVC源码有点了解的同窗应该不陌生了,是AbstractHandlerMethodAdapter中专门为了继承而设计的。
RequestMappingHandlerAdapter中的handleInternal最终调用的是invokeHandlerMethod,使用的是ServletInvocableHandlerMethod类的invokeAndHandle方法,ServletInvocableHandlerMethod算是对Controller中的方法的封装。
这里咱们主要看返回值的处理,返回值处理是使用的HandlerMethodReturnValueHandlerComposite,而HandlerMethodReturnValueHandlerComposite是RequestMappingHandlerAdapter的getDefaultReturnValueHandlers初始化的,真正处理返回值的是HandlerMethodReturnValueHandler。
前面咱们只是介绍了一下基本关联的知识,尚未介绍Unknown return value type出现的真正缘由,这里咱们就介绍一下真正的缘由。
咱们知道真正的业务逻辑是在Controller中,Spring MVC至关因而一个拦截器,帮助咱们处理了一些前置后置条件,好比将请求封装为参数对象,将返回对象转换为xml或者json格式等。
Unknown return value type出现的缘由就是处理返回值的时候出错了,具体抛出异常的位置就是在HandlerMethodReturnValueHandlerComposite的handleReturnValue方法之中,没有找到一个知足条件的HandlerMethodReturnValueHandler。
HandlerMethodReturnValueHandlerComposite中有不少HandlerMethodReturnValueHandler,handleReturnValue会根据顺序调用HandlerMethodReturnValueHandler的supportsReturnType方法,直到找到第一个HandlerMethodReturnValueHandler为止。
咱们来看一个最多见的HandlerMethodReturnValueHandler——RequestResponseBodyMethodProcessor的supportsReturnType方法。
@Override public boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); }
如上所示,是RequestResponseBodyMethodProcessor的supportsReturnType方法,咱们能够看到只要返回的类型上有ResponseBody注解,或者被调用的方法上有ResponseBody注解就能够使用RequestResponseBodyMethodProcessor来处理返回值。
这也就是为何不少同窗遇到Unknown return value type错误的第一经验就是在调用方法上添加ResponseBody的缘由。
***可是添加ResponseBody真的有效吗?***多数时候是有效的,由于通常是配置好的,只是忘了添加ResponseBody注解。
咱们只解决了部分问题,因此须要进一步的了解分析,RequestResponseBodyMethodProcessor是经过handleReturnValue方法来处理返回值的,最终调用的是AbstractMessageConverterMethodProcessor的writeWithMessageConverters。
这个时候就是HttpMessageConverter登场的时候了,固然RequestResponseBodyAdviceChain和RequestResponseBodyAdvice也在这里有出场的机会。
固然这里的HttpMessageConverter就是咱们前面介绍的在RequestMappingHandlerAdapter中初始化的化的HttpMessageConverter。
AbstractMessageConverterMethodProcessor的writeWithMessageConverters会按顺序遍历HttpMessageConverter调用canWrite,直到找到一个可用的HttpMessageConverter位置。
若是遇到一个No converter found for return value of type错误,那么就是返回值不是Null,可是有没有找到一个可用的MediaType。就是在getProducibleMediaTypes方法中没有找到一个能够使用的MediaType。
配置了message-converter:
<mvc:annotation-driven> <mvc:message-converters register-defaults="false"> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" /> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> <property name="writeAcceptCharset" value="false"/> </bean> </mvc:message-converters> </mvc:annotation-driven>
就必定不要在显式的配置:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
只有配置了annotation-driven,就会经过MvcNamespaceHandler注册AnnotationDrivenBeanDefinitionParser解析器,而AnnotationDrivenBeanDefinitionParser会自动注册RequestMappingHandlerMapping和RequestMappingHandlerAdapter这2个类。
若是在显式的配置,就会出现2个RequestMappingHandlerMapping和RequestMappingHandlerAdapter,这样会致使一些莫名其妙的错误,好比配置了FastJsonHttpMessageConverter可就是用不了,这是由于可能使用的是显式配置的RequestMappingHandlerAdapter,其中使用的是默认的HttpMessageConverter,而默认是没有配置FastJsonHttpMessageConverter的。
DispatcherServlet#service-->DispatcherServlet#doService-->DispatcherServlet#doDispatch-->HandlerAdapter#handle-->AbstractHandlerMethodAdapter#handleInternal
HttpMessageConverter的来源
MvcNameSpaceHandler
AnnotationDrivenBeanDefinitionParser注册默认HttpMessageConverter
解决问题的套路: 在DispatcherServlet中的doDispatcher方法中的getHandler和getHandlerAdapter处断点,查看Handler和HandlerAdapter,固然也能够检查handlerMappings中有哪些HandlerMapping,handlerAdapters中有哪些HandlerAdapter。
其实通常都不用看,基本都是RequestMappingHandlerMapping和RequestMappingHandlerAdapter这2个类,可是仍是须要检查一下是否是同一个实例,有时候由于配置问题致使多个实例出现。
而后须要重点关注的就是RequestResponseBodyMethodProcessor的handleReturnValue方法,这个方法比较简单,重点是AbstractMessageConverterMethodProcessor的writeWithMessageConverters方法和AbstractMessageConverterMethodProcessor的getProducibleMediaTypes方法。
固然也不必定是RequestResponseBodyMethodProcessor,因此须要关注RequestMappingHandlerAdapter的getDefaultInitBinderArgumentResolvers、getDefaultReturnValueHandlers和getDefaultArgumentResolvers方法,看看处理参数和返回值的类是什么,具体的对象是什么,参数和返回值类型是使用不一样的类。
调试的话断点到HandlerMethodArgumentResolverComposite的resolveArgument方法和HandlerMethodReturnValueHandlerComposite的handleReturnValue方法。