详解自定义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);
}

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

(0)

相关推荐

  • JavaEE开发之SpringMVC中的自定义消息转换器与文件上传

    本篇博客我们继续的来聊SpringMVC的东西,下方我们将会聊到js.css这些静态文件的加载配置,以及服务器推送的两种实现方式.当然我们在服务器推送时,会用到jQuery的东西,所以我们先聊一下如何加载静态资源文件,然后我们再聊如何实现服务器推送. 下方给出了两种实现服务器推送的方式,一种是SSE(Server Send Event (服务端推送事件))另一种是基于Servlet异步处理的推送,下方会给出详细的实现方式,并且给出了两者的区别. 一.静态资源文件映射 静态资源文件映射在Sprin

  • SpringMVC实现自定义类型转换器

    我们在使用SpringMVC时,常常需要把表单中的参数映射到我们对象的属性中,我们可以在默认的spring-servlet.xml加上如下的配置即可做到普通数据类型的转换,如将String转换成Integer和Double等: <mvc:annotation-driven /> 或 复制代码 代码如下: <bean id="conversionService" class="org.springframework.format.support.Formatt

  • springMVC4之强大类型转换器实例解析

    我们以自定义格式转换器的实现思路,来理解新架构的类型转换器的使用方法,同时在实际开发中,我们可能会有自己的格式转换需求,这个时候我们也可以通过自定义格式转换器来完成这些个性化需求. 自定义格式转换器 完成自定义转换器需要实现以下三个中的任意一个接口:Convertor<S,T>.GenericConvertor或ConvertorFacoty.下面我们对这些接口进行逐一分析: 1. Convertor<S,T> 这是最为简单的一个接口,定义了从源类到目标类的转换方法.该接口的定义如

  • SpringMVC源码解析之消息转换器HttpMessageConverter

    摘要 SpringMVC使用消息转换器实现请求报文和对象.对象和响应报文之间的自动转换 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制. #Http请求的抽象 还是回到请求-响应,也就是解析请求体,然后返回响应报文这个最基本的Http请求过程中来.我们知道,在servlet标准中,可以用j

  • springmvc实现自定义类型转换器示例

    springmvc除了自带的部分类型转换之外,还可以自定义类型转换器,按照以下步骤: 1.写一个类实现Converter接口 package com.hy.springmvc.entities; import org.springframework.core.convert.converter.Converter; import com.google.gson.Gson; public class DepartmentConvertor implements Converter<String,

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

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

  • Android 详解自定义圆角输入框和按钮的实现流程

    Android-自定义圆角输入框和按钮 我们的征程是星辰大海,而非人间烟尘 自定义圆角输入框 效果 1.在drawable/下面new Drawable Resources File 2.新建shape文件,在里面自定义xml文件样式 代码文件 <!-- res/drawable/button_shape_normal.xml --> <shape xmlns:android="http://schemas.android.com/apk/res/android" a

  • SpringBoot详解自定义Stater的应用

    目录 1.SpringBoot starter机制 2.为什么要自定义starter 3.自定义starter的命名规则 4.使用自定义starter 1.SpringBoot starter机制 SpringBoot由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之所以流行,也是因为starter.starter是SpringBoot非常重要的一部分,可以理解为一个可拔插式的插件,正是这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,

  • 详解自定义ajax支持跨域组件封装

    Class.create()分析 仿prototype创建类继承 var Class = { create: function () { var c = function () { this.request.apply(this, arguments); } for (var i = 0, il = arguments.length, it; i < il; i++) { it = arguments[i]; if (it == null) continue; Object.extend(c.p

  • 详解spring+springmvc+mybatis整合注解

    每天记录一点点,慢慢的成长,今天我们学习了ssm,这是我自己总结的笔记,大神勿喷!谢谢,主要代码!! ! spring&springmvc&mybatis整合(注解) 1.jar包 2.引入web.xml文件 <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param

  • 微信公众平台开发教程(五)详解自定义菜单

    一.概述: 如果只有输入框,可能太简单,感觉像命令行.自定义菜单,给我们提供了很大的灵活性,更符合用户的操作习惯.在一个小小的微信对话页面,可以实现更多的功能.菜单直观明了,不仅能提供事件响应,还支持URL跳转,如果需要的功能比较复杂,我们大可以使用URL跳转,跳转至我们的网页即可. 注意:自定义菜单,只有服务号才有此功能 接着我们详细介绍,如何实现自定义菜单? 二.详细步骤: 1.首先获取access_token access_token是公众号的全局唯一票据,公众号调用各接口时都需使用acc

  • 详解poi+springmvc+springjdbc导入导出excel实例

    工作中常遇到导入导出excel的需求,本獂有一简答实例与大家分享. 废话不多说, 1.所需jar包: 2.前端代码: ieport.jsp: <%@page import="java.util.Date"%> <%@ page language="java" contentType="text/html; charset=utf-" pageEncoding="utf-"%> <!DOCTYPE

  • 详解在springmvc中解决FastJson循环引用的问题

    我们先来看一个例子: package com.elong.bms; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import com.alibaba.fastjson.JSON; public class Test { public static void main(String[] args) { Map<String, Student> maps = new HashMap<

  • 详解利用SpringMVC拦截器控制Controller返回值

    背景:需求是在Controller中方法没有实现时,返回模拟结果.主要用于项目初期前台跟后台的交互,Web项目就是在前台发出请求然后后台响应并返回结果.本示例利用拦截器和注解实现跳过执行方法直接返回定义结构的功能. 通过定义一个StringResult注解,在访问方法的时候返回StringResult中的内容.通过Debug注解来定义方法是否要返回StringResult中的内容. Debug默认为TRUE package com.tiamaes.dep.annotation; import j

  • SpringMVC拦截器实现监听session是否过期详解

    本文主要向大家介绍了SpringMVC拦截器实现:当用户访问网站资源时,监听session是否过期的代码,具体如下: 一.拦截器配置 <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/user/login"/> <!-- 不拦截登录请求 --> <mvc:exclude-

随机推荐