SpringMVC修改返回值类型后的消息转换器处理方式

目录
  • 问题案例
  • 为什么?
  • 了解问题原因及分析
  • 解决方法
  • 结语

o(╯□╰)o这标题看起来有点奇怪,所以先以一个小小的案例来说明一下本文要描述和解决的问题

问题案例

假设有一个Controller方法如下

    @RequestMapping(value = "test")
    @ResponseBody
    public Object test() {
        Map<String,String> param = new HashMap<>();
        param.put("name","userwyh");
        return param;
    }

然后我们通过实现ResponseBodyAdvice接口对返回值再输出之前进行了修改,此处我们把它变成了String类型,直接返回hello,world。

@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        return "hello,world";
    }
}

这样不管Controller中的test方法返回什么值,我们都会把它变成hello,world输出。想想也没有什么不对,仔细确认了代码,它就是这样做的。

于是,启动项目,打开浏览器,地址栏输入localhost:8080/test,回车。

我们确实看到了hello,world字样,没有任何问题。

但是仔细一看的话,你会发现hello,world前后都多了一个引号。这显然不是我们想要的返回值啊!!!

为什么?

这时候标题提到的SpringMVC的消息转换器HttpMessageConverter就该出场了。

HttpMessageConverter源码剖析可以移步 SpringMVC源码剖析-消息转换器HttpMessageConverter 进行查看。我们这里就不对源码进行详细的解读了。

首先SpringMVC会加载在spring-servlet.xml配置好的消息转换器到messageConverters里。

protected final List<HttpMessageConverter<?>> messageConverters;

debug时发现SpringMVC不止加载了我们配置好的消息转换器,它还加载了另外7个默认的消息转换器,即便7个之中你在配置文件中配置了,它依然会再次加载一次。如图,0和1是配置的,2-8是默认加载的。

上图中的方法writeWithMessageConverters就是在Controller方法执行之后就进入的,在抽象类AbstractMessageConverterMethodProcessor的第164行处。这个方法也正是SpringMVC为当前返回值选择合适的消息转换器,选择的顺序就是messageConverters的转换器顺序。

通过阅读源码,我们知道,此处对messageConverters进行了遍历,先判断当前的转换器对当前返回类型是否能写canWrite,如果能得话就会调用beforeBodyWrite方法,然后把beforeBodyWrite的返回值通过write方法进行输出。如果不能的话就选择下一个转换器。如果最终没有一个合适的,就会抛出一个异常。

了解问题原因及分析

有了上面对HttpMessageConverter的简单描述,我们大概可以得到一个结论:

因为在Controller中的返回值类型是java.util.HashMap,所以在writeWithMessageConverters方法中SpringMVC选定的转换器并不是StringHttpMessageConverter,而是MappingJackson2HttpMessageConverter。

我们可以通过在MyResponseBodyAdvice类beforeBodyWrite方法中打印参数得以证明确实当前SpringMVC选择的转换器就是MappingJackson2HttpMessageConverter。

然后我们在beforeBodyWrite执行返回了String类型的hello,world。而此时选定的转换器已经是MappingJackson2HttpMessageConverter了,所以通过该转换器进行转换输出。

我们再通过一个实例说明MappingJackson2HttpMessageConverter会把String前后新增双引号。

通过上面的分析,相信大家已经大概知道问题的来龙去脉了。

所以第一个反应当然就是重写MappingJackson2HttpMessageConverter的某个方法咯。

不过在这之前,我们还需要对SpringMVC的源码进行进一步分析。

上面提到它会把beforeBodyWrite的返回值通过write方法进行输出,所以我们需要了解这个write方法。它是一个接口,由具体的消息转换器进行实现。SpringMVC自己提供了一个抽象类AbstractGenericHttpMessageConverter进行了实现,但把具体的write任务交给了抽象方法writeInternal。如下图

接下来就是看MappingJackson2HttpMessageConverter的代码了。该类的父类AbstractJackson2HttpMessageConverter确实继承了AbstractGenericHttpMessageConverter并实现了writeInternal方法。

所以,方法很简单,我们只需要把jackson的writeInternal重写一下就可以了。

解决方法

1、创建一个MappingJackson2HttpMessageConverterFactory类

package com.userwyh.spring.controller;
import org.slf4j.Logger;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import static org.slf4j.LoggerFactory.getLogger;
/**
 * Created by userwyh on 2017/3/4.
 */
public class MappingJackson2HttpMessageConverterFactory {
    private static final Logger logger = getLogger(MappingJackson2HttpMessageConverterFactory.class);
    public MappingJackson2HttpMessageConverter init() {
        return new MappingJackson2HttpMessageConverter(){
            /**
             * 重写Jackson消息转换器的writeInternal方法
             * SpringMVC选定了具体的消息转换类型后,会调用具体类型的write方法,将Java对象转换后写入返回内容
             */
            @Override
            protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
                if (object instanceof String){
                    logger.info("在MyResponseBodyAdvice进行转换时返回值变成String了,不能用原来选定消息转换器进行转换,直接使用StringHttpMessageConverter转换");
                    //StringHttpMessageConverter中就是用以下代码写的
                    Charset charset = this.getContentTypeCharset(outputMessage.getHeaders().getContentType());
                    StreamUtils.copy((String)object, charset, outputMessage.getBody());
                }else{
                    logger.info("返回值不是String类型,还是使用之前选择的转换器进行消息转换");
                    super.writeInternal(object, type, outputMessage);
                }
            }
            private Charset getContentTypeCharset(MediaType contentType) {
                return contentType != null && contentType.getCharset() != null?contentType.getCharset():this.getDefaultCharset();
            }
        };
    }
}

2、稍微修改一下spring的配置文件

<mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>image/jpeg</value>
                        <value>image/png</value>
                        <value>image/gif</value>
                    </list>
                </property>
            </bean>
            <bean  factory-bean="mappingJackson2HttpMessageConverterFactory" factory-method="init"
                   class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <bean id="mappingJackson2HttpMessageConverterFactory" class = "com.userwyh.spring.controller.MappingJackson2HttpMessageConverterFactory" />

此时SpringMVC启动时,messageConverters的顺序就是ByteArrayHttpMessageConverter,mappingJackson2HttpMessageConverterFactory,然后另外7个默认的,共9个。如第一张截图所示即为配置后的效果。

3、启动项目,打开浏览器,地址栏输入localhost:8080/test,回车。双引号没有了,正是我们想要的结果。

再看一下日志:

三月 05, 2017 11:01:51 下午 com.userwyh.spring.controller.MappingJackson2HttpMessageConverterFactory$1 writeInternal 信息: 在MyResponseBodyAdvice进行转换时返回值变成String了,不能用原来选定消息转换器进行转换,直接使用StringHttpMessageConverter转换

结语

其实你可以直接在Controller中直接返回String类型的?

其实你可以针对在MyResponseBodyAdvice 中确认要返回不同类型的,直接在Controller中判断下就行了啊,比如以下这样就可以了,为什么要这么麻烦呢?

    @RequestMapping(value = "test")
    @ResponseBody
    public Object test() {
        Map<String,String> param = new HashMap<>();
        param.put("name","userwyh");
        if(condition){
            return "hello,world";
        }
        return param;
    }

可能,因为喜欢折腾,既然可以在MyResponseBodyAdvice 进行统一的返回值转换,我就断定可以找到方法解决这个问题的,也确实解决了。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Springmvc自定义类型转换器实现步骤

    一.什么是springmvc类型转换器? 在我们的ssm框架中,前端传递过来的参数都是字符串,在controller层接收参数的时候springmvc能够帮我们将大部分字符串类型的参数自动转换为我们指定的参数,这就是springmvc为我们提供的类型转换器.但是springmvc提供的类型转换器只能够转换指定格式的参数,例如:我们参数中传递time=2020/08/18,在controller中我们就可以通过Date类型的参数接收它,springmvc能够自动将该日期字符串转换为日期对象.但是如

  • 详解自定义SpringMVC的Http信息转换器的使用

    在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制.使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上. HttpInputMessage 这个类是SpringMVC内部对一次Http请求报文的抽象,在HttpMessageConverter的read()方法中,有一个HttpInputMessage的形参,它正是SpringM

  • spring消息转换器使用详解

    本文实例为大家分享了spring消息转换器的具体代码,供大家参考,具体内容如下 //domain package com.crazy.goods.tools; /** * 0755-351512 * @author Administrator * */ public class Phone { private String qno; private String number; public String getQno() { return qno; } public void setQno(S

  • Spring MVC Controller返回值及异常的统一处理方法

    旧的设计方案 开发api的时候,需要先定义好接口的数据响应结果.如下是一个很简单直接的Controller实现方法及响应结果定义. @RestController @RequestMapping("/users") public class UserController { @Inject private UserService userService; @GetRequest("/{userId:\\d+}") public ResponseBean signin

  • SpringMVC修改返回值类型后的消息转换器处理方式

    目录 问题案例 为什么? 了解问题原因及分析 解决方法 结语 o(╯□╰)o这标题看起来有点奇怪,所以先以一个小小的案例来说明一下本文要描述和解决的问题 问题案例 假设有一个Controller方法如下 @RequestMapping(value = "test") @ResponseBody public Object test() { Map<String,String> param = new HashMap<>(); param.put("na

  • C++返回值类型后置实现(跟踪返回值类型)

    在泛型编程中,可能需要通过参数的运算来得到返回值的类型.考虑下面这个场景: template <typename R, typename T, typename U> R add(T t, U u) { return t+u; } int a = 1; float b = 2.0; auto c = add<decltype(a + b)>(a, b); 我们并不关心 a+b 的类型是什么,因此,只需要通过 decltype(a+b) 直接得到返回值类型即可.但是像上面这样使用十分

  • SpringMVC Controller 返回值的可选类型详解

    spring mvc 支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, String, void. ModelAndView @RequestMapping("/hello") public ModelAndView helloWorld() { String message = "Hello World, Spring 3.x!"; return new ModelAndView("hello"

  • Array.slice()与Array.splice()的返回值类型

    最近做个练习,用到Array.slice()和Array.splice(),以前没有注意其返回值类型 Array.slice()返回Array -- 一个由原始数组中某一范围的元素构成的数组. Array.splice()返回Array -- 包含从原始数组中删除的元素的一个数组. 返回的是一个数组,用typeof()看一下是object,这没错!需要提醒大家的是: 当数组元素是数字时,用Array.slice()和Array.splice()取其中某个元素进行大小比较,可得要注意,必须转换 类

  • C# WebApi 接口返回值不困惑:返回值类型详解

    前言:已经有一个月没写点什么了,感觉心里空落落的.今天再来篇干货,想要学习Webapi的园友们速速动起来,跟着博主一起来学习吧.之前分享过一篇C#进阶系列--WebApi接口传参不再困惑:传参详解,这篇博文内容本身很基础,没想到引起很多园友关注,感谢大家的支持.作为程序猿,我们都知道参数和返回值是编程领域不可分割的两大块,此前分享了下WebApi的传参机制,今天再来看看WebApi里面另一个重要而又基础的知识点:返回值.还是那句话:本篇针对初初使用WebApi的同学们,比较基础,有兴趣的且看看.

  • typescript返回值类型和参数类型的具体使用

    目录 返回值类型 可缺省和可推断的返回值类型 Generator 函数的返回值 参数类型 可选参数和默认参数 剩余参数 返回值类型 在 JavaScript 中,我们知道一个函数可以没有显式 return,此时函数的返回值应该是 undefined: function fn() { // TODO } console.log(fn()); // => undefined 需要注意的是,在 TypeScript 中,如果我们显式声明函数的返回值类型为 undfined,将会得到如下所示的错误提醒.

  • php mysqli查询语句返回值类型实例分析

    本文实例分析了php mysqli查询语句返回值类型.分享给大家供大家参考,具体如下: <?php $link = new mysqli('localhost', 'root','123','test'); $sql = 'select uName from userInfo'; $a = $link->query($sql); echo '<pre>'; echo '有结果集<br>'; var_dump($a); echo '</pre>'; $sql

  • PowerShell函数指定返回值类型实例

    本文介绍在自定义PowerShell函数时,如何设置返回值的数据类型.PowerShell函数的返回值可以有类型,也可以没有类型,跟输入参数相似.     定义PowerShell函数的返回值类型,要使用OutputType这个指令.将这个指令放到param指令之前即可实现对返回值类型的定义. 复制代码 代码如下: function Test-IntelliSense {     [OutputType('System.DateTime')]     param()     return Get

  • Ajax返回值类型与用法实例分析

    本文实例讲述了Ajax返回值类型与用法.分享给大家供大家参考,具体如下: Ajax返回值类型主要有XML类型和文本类型,其中文本类型又可以分为HTML.json类型等. 1.返回值之XML类型 如果服务器的响应头中Content-type的内容为text/xml时,此时XMLHttpRequest对象的responseXML属性才能使用. 2.返回值之文本类型 文本类型主要分为Html类型和json类型. (1)Html类型 使用场景:一般返回需要重复复杂的操作.比如,页面使用ajax从服务器请

  • ASP.NET Core中的Action的返回值类型实现

    在Asp.net Core之前所有的Action返回值都是ActionResult,Json(),File()等方法返回的都是ActionResult的子类.并且Core把MVC跟WebApi合并之后Action的返回值体系也有了很大的变化. ActionResult类 ActionResult类是最常用的返回值类型.基本沿用了之前Asp.net MVC的那套东西,使用它大部分情况都没问题.比如用它来返回视图,返回json,返回文件等等.如果是异步则使用Task. public class Te

随机推荐