五、自定义SpringMVC的Http信息转化器

本文详细介绍了在SpringMVC中如何自定义HttpMessageConverter,特别是针对JSON转换的MappingJackson2HttpMessageConverter。内容包括HttpInputMessage和HttpOutputMessage的作用,HttpMessageConverter的接口方法以及默认提供的转换器。还探讨了请求和响应匹配的过程,并提供了自定义JSON转换器的步骤和配置方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制。使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上。

HttpInputMessage

这个类是SpringMVC内部对一次Http请求报文的抽象,在HttpMessageConverter的read()方法中,有一个HttpInputMessage的形参,它正是SpringMVC的消息转换器所作用的受体“请求消息”的内部抽象,消息转换器从“请求消息”中按照规则提取消息,转换为方法形参中声明的对象。

package org.springframework.http;

import java.io.IOException;
import java.io.InputStream;

public interface HttpInputMessage extends HttpMessage {

    InputStream getBody() throws IOException;

}

HttpOutputMessage

在HttpMessageConverter的write()方法中,有一个HttpOutputMessage的形参,它正是SpringMVC的消息转换器所作用的受体“响应消息”的内部抽象,消息转换器将“响应消息”按照一定的规则写到响应报文中。

package org.springframework.http;

import java.io.IOException;
import java.io.OutputStream;

public interface HttpOutputMessage extends HttpMessage {

    OutputStream getBody() throws IOException;

}

HttpMessageConverter

/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.http.converter;

import java.io.IOException;
import java.util.List;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;


public interface HttpMessageConverter<T> {


    boolean canRead(Class<?> clazz, MediaType mediaType);

    boolean canWrite(Class<?> clazz, MediaType mediaType);

    List<MediaType> getSupportedMediaTypes();


    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;


    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;

}

HttpMessageConverter 接口提供了5个方法:


  • canRead :判断该转换器是否能将请求内容转换成Java对象
  • canWrite :判断该转换器是否可以将Java对象转换成返回内容
  • getSupportedMediaTypes :获得该转换器支持的MediaType类型
  • read :读取请求内容并转换成Java对象
  • write :将Java对象转换后写入返回内容

其中 read 和 write 方法的参数分别有有 HttpInputMessage 和 HttpOutputMessage 对象,这两个对象分别代表着一次Http通讯中的请求和响应部分,可以通过 getBody 方法获得对应的输入流和输出流。
当前Spring中已经默认提供了相当多的转换器,分别有:

名称作用读支持MediaType写支持MediaType
ByteArrayHttpMessageConverter数据与字节数组的相互转换/application/octet-stream
StringHttpMessageConverter数据与String类型的相互转换text/*text/plain
FormHttpMessageConverter表单与MultiValueMap<string, string=””>的相互转换application/x-www-form-urlencodedapplication/x-www-form-urlencoded
SourceHttpMessageConverter数据与javax.xml.transform.Source的相互转换text/xml和application/xmltext/xml和application/xml
MarshallingHttpMessageConverter使用SpringMarshaller/Unmarshaller转换XML数据text/xml和application/xmltext/xml和application/xml
MappingJackson2HttpMessageConverter使用Jackson的ObjectMapper转换Json数据application/jsonapplication/json
MappingJackson2XmlHttpMessageConverter使用Jackson的XmlMapper转换XML数据application/xmlapplication/xml
BufferedImageHttpMessageConverter数据与java.awt.image.BufferedImage的相互转换Java I/O API支持的所有类型Java I/O API支持的所有类型

HttpMessageConverter匹配过程:

@RequestBody注解时: 根据Request对象header部分的Content-Type类型,逐一匹配合适的HttpMessageConverter来读取数据。

private Object readWithMessageConverters(MethodParameter methodParam, HttpInputMessage inputMessage, Class paramType) throws Exception {  

    MediaType contentType = inputMessage.getHeaders().getContentType();  
    if (contentType == null) {  
        StringBuilder builder = new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType()));  
        String paramName = methodParam.getParameterName();  
        if (paramName != null) {  
            builder.append(' ');  
            builder.append(paramName);  
        }  
        throw new HttpMediaTypeNotSupportedException("Cannot extract parameter (" + builder.toString() + "): no Content-Type found");  
    }  

    List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();  
    if (this.messageConverters != null) {  
        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {  
            allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());  
            if (messageConverter.canRead(paramType, contentType)) {  
                if (logger.isDebugEnabled()) {  
                    logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType  + "\" using [" + messageConverter + "]");  
                }  
                return messageConverter.read(paramType, inputMessage);  
            }  
        }  
    }  
    throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);  
}

@ResponseBody注解时:根据Request对象header部分的Accept属性(逗号分隔),逐一按accept中的类型,去遍历找到能处理的HttpMessageConverter。

private void writeWithMessageConverters(Object returnValue,  HttpInputMessage inputMessage, HttpOutputMessage outputMessage)  
                throws IOException, HttpMediaTypeNotAcceptableException {  
    List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();  
    if (acceptedMediaTypes.isEmpty()) {  
        acceptedMediaTypes = Collections.singletonList(MediaType.ALL);  
    }  
    MediaType.sortByQualityValue(acceptedMediaTypes);  
    Class<?> returnValueType = returnValue.getClass();  
    List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();  
    if (getMessageConverters() != null) {  
        for (MediaType acceptedMediaType : acceptedMediaTypes) {  
            for (HttpMessageConverter messageConverter : getMessageConverters()) {  
                if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {  
                    messageConverter.write(returnValue, acceptedMediaType, outputMessage);  
                    if (logger.isDebugEnabled()) {  
                        MediaType contentType = outputMessage.getHeaders().getContentType();  
                        if (contentType == null) {  
                            contentType = acceptedMediaType;  
                        }  
                        logger.debug("Written [" + returnValue + "] as \"" + contentType +  
                                "\" using [" + messageConverter + "]");  
                    }  
                    this.responseArgumentUsed = true;  
                    return;  
                }  
            }  
        }  
        for (HttpMessageConverter messageConverter : messageConverters) {  
            allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());  
        }  
    }  
    throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);  
}

自定义一个JSON转换器

class CustomJsonHttpMessageConverter implements HttpMessageConverter {

    //Jackson的Json映射类
    private ObjectMapper mapper = new ObjectMapper();

    //该转换器的支持类型:application/json
    private List supportedMediaTypes = Arrays.asList(MediaType.APPLICATION_JSON);

    /**
     * 判断转换器是否可以将输入内容转换成Java类型
     * @param clazz     需要转换的Java类型
     * @param mediaType 该请求的MediaType
     * @return
     */
    @Override
    public boolean canRead(Class clazz, MediaType mediaType) {
        if (mediaType == null) {
            return true;
        }
        for (MediaType supportedMediaType : getSupportedMediaTypes()) {
            if (supportedMediaType.includes(mediaType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断转换器是否可以将Java类型转换成指定输出内容
     * @param clazz     需要转换的Java类型
     * @param mediaType 该请求的MediaType
     * @return
     */
    @Override
    public boolean canWrite(Class clazz, MediaType mediaType) {
        if (mediaType == null || MediaType.ALL.equals(mediaType)) {
            return true;
        }
        for (MediaType supportedMediaType : getSupportedMediaTypes()) {
            if (supportedMediaType.includes(mediaType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获得该转换器支持的MediaType
     * @return
     */
    @Override
    public List getSupportedMediaTypes() {
        return supportedMediaTypes;
    }

    /**
     * 读取请求内容,将其中的Json转换成Java对象
     * @param clazz         需要转换的Java类型
     * @param inputMessage  请求对象
     * @return
     * @throws IOException
     * @throws HttpMessageNotReadableException
     */
    @Override
    public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return mapper.readValue(inputMessage.getBody(), clazz);
    }

    /**
     * 将Java对象转换成Json返回内容
     * @param o             需要转换的对象
     * @param contentType   返回类型
     * @param outputMessage 回执对象
     * @throws IOException
     * @throws HttpMessageNotWritableException
     */
    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        mapper.writeValue(outputMessage.getBody(), o);
    }
}

自定义MappingJackson2HttpMessage

从 MappingJackson2HttpMessageConverter 的父类 AbstractHttpMessageConverter 中的 write 方法可以看出,该方法通过 writeInternal 方法向返回结果的输出流中写入数据,所以只需要重写该方法即可:

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    return new MappingJackson2HttpMessageConverter() {
        //重写writeInternal方法,在返回内容前首先进行加密
        @Override
        protected void writeInternal(Object object,
                                     HttpOutputMessage outputMessage) throws IOException,
                HttpMessageNotWritableException {
            //使用Jackson的ObjectMapper将Java对象转换成Json String
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(object);
            LOGGER.error(json);
            //加密
            String result = json + "加密了!";
            LOGGER.error(result);
            //输出
            outputMessage.getBody().write(result.getBytes());
        }
    };
}

在这之后还需要将这个自定义的转换器配置到Spring中,这里通过重写 WebMvcConfigurer 中的 configureMessageConverters 方法添加自定义转换器:

//添加自定义转换器
@Override
public void configureMessageConverters(List<httpmessageconverter<?>> converters) {
    converters.add(mappingJackson2HttpMessageConverter());
    super.configureMessageConverters(converters);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值