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

 更新时间:2017年11月30日 09:30:49   作者:等风de帆  
这篇文章主要介绍了详解自定义SpringMVC的Http信息转换器的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

在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个方法:

  1. canRead :判断该转换器是否能将请求内容转换成Java对象
  2. canWrite :判断该转换器是否可以将Java对象转换成返回内容
  3. getSupportedMediaTypes :获得该转换器支持的MediaType类型
  4. read :读取请求内容并转换成Java对象
  5. 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-urlencoded application/x-www-form-urlencoded
SourceHttpMessageConverter 数据与javax.xml.transform.Source的相互转换 text/xml和application/xml text/xml和application/xml
MarshallingHttpMessageConverter 使用SpringMarshaller/Unmarshaller转换XML数据 text/xml和application/xml text/xml和application/xml
MappingJackson2HttpMessageConverter 使用Jackson的ObjectMapper转换Json数据 application/json application/json
MappingJackson2XmlHttpMessageConverter 使用Jackson的XmlMapper转换XML数据 application/xml application/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);
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 如何使用MAVEN打JAR包(直接使用)

    如何使用MAVEN打JAR包(直接使用)

    这篇文章主要介绍了如何使用MAVEN打JAR包(直接使用),文中通过实例代码介绍了maven 使用assembly插件进行打包的方法,需要的朋友可以参考下
    2023-03-03
  • 使用Logback设置property参数方式

    使用Logback设置property参数方式

    这篇文章主要介绍了使用Logback设置property参数方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Java运行时环境之ClassLoader类加载机制详解

    Java运行时环境之ClassLoader类加载机制详解

    这篇文章主要给大家介绍了关于Java运行时环境之ClassLoader类加载机制的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-01-01
  • MyBatis-Plus Sequence主键的实现

    MyBatis-Plus Sequence主键的实现

    这篇文章主要介绍了MyBatis-Plus Sequence主键的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • 基于SSM+Shiro+Bootstrap实现用户权限管理系统

    基于SSM+Shiro+Bootstrap实现用户权限管理系统

    这篇文章主要介绍了基于SSM+Shiro实现一个用户权限管理系统,每位用户只可访问指定的页面,文中的示例代码讲解详细,对我们学习或工作有一定帮助,快跟随小编一起学习吧
    2021-12-12
  • 自定义spring mvc的json视图实现思路解析

    自定义spring mvc的json视图实现思路解析

    这篇文章主要介绍了自定义spring mvc的json视图的实现思路解析,本文给大家介绍的非常详细,具有参考借鉴价值,需要的朋友可以参考下
    2017-12-12
  • Java实现跳跃表(skiplist)的简单实例

    Java实现跳跃表(skiplist)的简单实例

    这篇文章主要介绍了Java编程中跳跃表的概念和实现原理,并简要叙述了它的结构,具有一定参考价值,需要的朋友可以了解下。
    2017-09-09
  • 详解用java描述矩阵求逆的算法

    详解用java描述矩阵求逆的算法

    这篇文章主要介绍了用java描述矩阵求逆的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Java中static和static final的区别详解

    Java中static和static final的区别详解

    这篇文章主要介绍了Java中static和static final的区别详解,开发时我们经常用到static以及static final来修饰我们的字段变量,那么他们到底有什么区别呢?其实他们的区别可以用使用字节码文件来解析,需要的朋友可以参考下
    2023-10-10
  • MyBatis-Plus实现逻辑删除功能解析

    MyBatis-Plus实现逻辑删除功能解析

    这篇文章主要介绍了MyBatis-Plus实现逻辑删除功能解析,有时候并不需要真正的删除数据,而是想逻辑删除,方便数据恢复,MyBatis-Plus可以很方便的实现逻辑删除的功能,需要的朋友可以参考下
    2023-11-11

最新评论