3.盘点springmvc的常用接口之HttpMessageConverter

Wesley13
• 阅读 877

3. 盘点springmvc的常用接口之HttpMessageConverter###

前言

举例:

POST http://localhost:8080/demo3

传入富文本数据流:Bill Gates

在controller中获得Person对象并响应Person内容:Bill Gates

原始写法:

@RequestMapping(method = RequestMethod.POST)
public void postPerson(HttpServletRequest request, HttpServletResponse response) {
  try {
    String content = new String(IOUtils.toByteArray(request.getInputStream()));
    String[] strs = content.split("\\s");
    Person person = new Person(strs[0], strs[1]);

    // TODO do something for person
    log.info(person.toString());

    String text = person.getFirstName() + " " + person.getLastName();
    response.getWriter().write(text);
  } catch (IOException e) {
    e.printStackTrace();
  }
}

可以看到原始写法把实体的序列化反序列化过程都堆叠在了controller中,造成controller的混乱和不易阅读。

在springmvc中,我们可以使用@RequestBody@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换。底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。

比如我传入的json或者xml格式的报文数据流,要在服务器进行逻辑处理必须事先转换成实体封装。而在进行数据获取请求时,实体对象又要转换成json或xml其他格式的数据流输出。类似这样的实体序列化和反序列化的工作正是由org.springframework.http.converter.HttpMessageConverter接口实现的。

在springmvc中内置了众多的HttpMessageConverter实现类,一般情况下无需自定义实现即可满足业务需求。在配置<mvc:annotation-driven/>或spring-boot的@EnableWebMvc注解时自动加载如下实现:

  • org.springframework.http.converter.ByteArrayHttpMessageConverter
  • org.springframework.http.converter.StringHttpMessageConverter
  • org.springframework.http.converter.ResourceHttpMessageConverter
  • org.springframework.http.converter.xml.SourceHttpMessageConverter
  • org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
  • org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter
  • org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

springmvc在使用@RequestBody接收数据时会依据请求头Content-Type值依次调用上述默认配置的converter的canRead方法判断该使用哪个转换器。在使用@ResponseBody响应数据时会依据请求头Accept值依次调用converter的canWrite方法。所以假如自定义的HttpMessageConverter有相同的MediaType时需要注册在默认转换器之前。

接口说明

public interface HttpMessageConverter<T> {
    //用于检验是否可以读入数据执行read方法
    boolean canRead(Class<?> clazz, MediaType mediaType);
    //用于检验是否可以写入数据执行write方法
    boolean canWrite(Class<?> clazz, MediaType mediaType);
    //返回可以支持该消息转换器的Media类型
    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;

}

泛型T为实体类型。

示例1

改写原始写法

package com.demo.mvc.component;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import com.demo.domain.Person;

public class PersonHttpMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        if (clazz == Person.class) {
            if (mediaType == null) {
                return true;
            }
            for (MediaType supportedMediaType : getSupportedMediaTypes()) {
                if (supportedMediaType.includes(mediaType)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        if (clazz == Person.class) {
            if (mediaType == null || MediaType.ALL.equals(mediaType)) {
                return true;
            }
            for (MediaType supportedMediaType : getSupportedMediaTypes()) {
                if (supportedMediaType.isCompatibleWith(mediaType)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        MediaType mediaType = new MediaType("text", "person", Charset.forName("UTF-8"));
        return Collections.singletonList(mediaType);
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        String content = new String(IOUtils.toByteArray(inputMessage.getBody()));
        String[] strs = content.split("\\s");
        return new Person(strs[0], strs[1]);
    }

    @Override
    public void write(Person person, MediaType mediaType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        String content = person.getFirstName() + " " + person.getLastName();
        IOUtils.write(content, outputMessage.getBody());
    }

}

注册该消息转换器

spring-boot

package com.demo;

import java.util.List;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import com.demo.mvc.component.PersonHttpMessageConverter;

@SpringBootApplication
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new PersonHttpMessageConverter());
    }
}

或xml配置

<mvc:annotation-driven>
    <mvc:message-converters>
         <bean class="com.demo.mvc.component.PersonHttpMessageConverter" />
    </mvc:message-converters>
</mvc:annotation-driven>

controller

package com.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.demo.domain.Person;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("demo3")
public class HttpMessageConverterDemoController {

    @ResponseBody
    @RequestMapping(method = RequestMethod.POST)
    public Person postPerson(@RequestBody Person person) {
        log.info(person.toString());
        return person;
    }
}

示例2

直接实现HttpMessageConverter接口比较复杂,需要自行处理判断MediaType的逻辑。通常自定义时只需要继承自org.springframework.http.converter.AbstractHttpMessageConverter抽象类即可,该类已经帮助我们完成了判断MediaType的逻辑。

package com.demo.mvc.component;

import java.io.IOException;
import java.nio.charset.Charset;

import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import com.demo.domain.Person;

public class PersonHttpMessageConverterExtendsAbstract extends AbstractHttpMessageConverter<Person> {

    public PersonHttpMessageConverterExtendsAbstract() {
        super(new MediaType("text", "person", Charset.forName("UTF-8")));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return clazz == Person.class;
    }

    @Override
    protected Person readInternal(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        String content = new String(IOUtils.toByteArray(inputMessage.getBody()));
        String[] strs = content.split("\\s");
        return new Person(strs[0], strs[1]);
    }

    @Override
    protected void writeInternal(Person person, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        String content = person.getFirstName() + " " + person.getLastName();
        IOUtils.write(content, outputMessage.getBody());
    }

}

该抽象类提供了supports方法只需我们验证实体类。

readInternal方法等同于接口的read,参看AbstractHttpMessageConverter源码发现read直接调用了readInternal,而writeInternal也差不多等同于write,只是当参数HttpOutputMessageStreamingHttpOutputMessage时另行处理了。

AbstractHttpMessageConverter源码:

/**
     * This implementation simple delegates to {@link #readInternal(Class, HttpInputMessage)}.
     * Future implementations might add some default behavior, however.
     */
@Override
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException {
  return readInternal(clazz, inputMessage);
}

/**
       * This implementation sets the default headers by calling {@link #addDefaultHeaders},
     * and then calls {@link #writeInternal}.
     */
@Override
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
  throws IOException, HttpMessageNotWritableException {

  final HttpHeaders headers = outputMessage.getHeaders();
  addDefaultHeaders(headers, t, contentType);

  if (outputMessage instanceof StreamingHttpOutputMessage) {
    StreamingHttpOutputMessage streamingOutputMessage =
      (StreamingHttpOutputMessage) outputMessage;
    streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
      @Override
      public void writeTo(final OutputStream outputStream) throws IOException {
        writeInternal(t, new HttpOutputMessage() {
          @Override
          public OutputStream getBody() throws IOException {
            return outputStream;
          }
          @Override
          public HttpHeaders getHeaders() {
            return headers;
          }
        });
      }
    });
  }
  else {
    writeInternal(t, outputMessage);
    outputMessage.getBody().flush();
  }
}

友情链接:

盘点springmvc的常用接口目录

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Wesley13 Wesley13
3年前
P2P技术揭秘.P2P网络技术原理与典型系统开发
Modular.Java(2009.06)\.Craig.Walls.文字版.pdf:http://www.t00y.com/file/59501950(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.t00y.com%2Ffile%2F59501950)\More.E
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这