HttpMessageConverter的注册

简介

Spring MVC遇到一个Unknown return value type XXX的错误,很多有经验的同学可能会会说这简单在Controller的方法上加上ResponseBody注解就可以了。

对于很多时候是有效的,因为这是我们常见的错误,但是但是也有无效的时候,我们这篇文章就来梳理一下为什么会出现这个问题。

最重要的是我们将简单的梳理一下Spring MVC中的一些关键步骤,让大家对整个数据的流转有一个更加清晰的认识。

RequestMappingHandlerAdapter

HandlerAdapter在Spring MVC中的重要性就不必多说了,如果你时间不多,那么建议直接了解RequestMappingHandlerAdapter这一个HandlerAdapter就可以了,因为这是我们使用的最多的,在高版本中如果没有配置默认使用的就是RequestMappingHandlerAdapter。

HttpMessageConverter

对于RequestMappingHandlerAdapter我们就奔着最常见的说,就是我们使用最多的HttpMessageConverter。

RequestMappingHandlerAdapter的HttpMessageConverter设置有3中方式:

  1. 没有配置,默认使用的构造函数中添加的HttpMessageConverter
  2. 通过xml中的mvc:annotation-driven的子标签mvc:message-converters设置
  3. RequestMappingHandlerAdapter#setMessageConverters方法设置

第2中应该是最常见的配置HttpMessageConverter的方式了。下面我们看一下这些设置方式有什么不同。

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。

register-defaults设置为true的前提下,如果依赖中有jackson就会注册MappingJackson2HttpMessageConverter,如果依赖中有gson就会注册GsonHttpMessageConverter。

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

转载于:https://my.oschina.net/u/2474629/blog/3025954

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值