HttpMessageConverter的註冊

簡介

Spring MVC遇到一個Unknown return value type XXX的錯誤,不少有經驗的同窗可能會會說這簡單在Controller的方法上加上ResponseBody註解就能夠了。java

對於不少時候是有效的,由於這是咱們常見的錯誤,可是可是也有無效的時候,咱們這篇文章就來梳理一下爲何會出現這個問題。web

最重要的是咱們將簡單的梳理一下Spring MVC中的一些關鍵步驟,讓你們對整個數據的流轉有一個更加清晰的認識。spring

RequestMappingHandlerAdapter

HandlerAdapter在Spring MVC中的重要性就沒必要多說了,若是你時間很少,那麼建議直接瞭解RequestMappingHandlerAdapter這一個HandlerAdapter就能夠了,由於這是咱們使用的最多的,在高版本中若是沒有配置默認使用的就是RequestMappingHandlerAdapter。json

HttpMessageConverter

對於RequestMappingHandlerAdapter咱們就奔着最多見的說,就是咱們使用最多的HttpMessageConverter。mvc

RequestMappingHandlerAdapter的HttpMessageConverter設置有3中方式:app

  1. 沒有配置,默認使用的構造函數中添加的HttpMessageConverter
  2. 經過xml中的mvc:annotation-driven的子標籤mvc:message-converters設置
  3. RequestMappingHandlerAdapter#setMessageConverters方法設置

第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與invokeHandlerMethod

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來源

HttpMessageConverter的來源

MvcNameSpaceHandler

MvcNameSpaceHandler

GetMessageConverter

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方法。

相關文章
相關標籤/搜索