AbstractHttpMessageConverter
一个基础抽象实现,它也还是个泛型类。对于泛型的控制,有如下特点:
- 最广的可以选择Object,不过Object并不都是可以序列化的,但是子类可以在覆盖的supports方法中进一步控制,因此选择Object是可以的
- 最符合的是Serializable,既完美满足泛型定义,本身也是个Java序列化/反序列化的充要条件
- 自定义的基类Bean,有些技术规范要求自己代码中的所有bean都继承自同一个自定义的基类BaseBean,这样可以在Serializable的基础上再进一步控制,满足自己的业务要求
若我们自己需要自定义一个消息转换器,大多数情况下也是继承抽象类再具体实现。比如我们最熟悉的:FastJsonHttpMessageConverter它就是一个子类实现
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> { // 它主要内部维护了这两个属性,可议构造器赋值,也可以set方法赋值~~ private List<MediaType> supportedMediaTypes = Collections.emptyList(); @Nullable private Charset defaultCharset; // supports是个抽象方法,交给子类自己去决定自己支持的转换类型~~~~ // 而canRead(mediaType)表示MediaType也得在我支持的范畴了才行(入参MediaType若没有指定,就返回true的) @Override public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) { return supports(clazz) && canRead(mediaType); } // 原理基本同上,supports和上面是同一个抽象方法 所以我们发现并不能入参处理Map,出餐处理List等等 @Override public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) { return supports(clazz) && canWrite(mediaType); } // 这是Spring的惯用套路:readInternal 虽然什么都没做,但我觉得还是挺有意义的。Spring后期也非常的好扩展了~~~~ @Override public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return readInternal(clazz, inputMessage); } // 整体上就write方法做了一些事~~ @Override public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { final HttpHeaders headers = outputMessage.getHeaders(); // 设置一个headers.setContentType 和 headers.setContentLength addDefaultHeaders(headers, t, contentType); if (outputMessage instanceof StreamingHttpOutputMessage) { StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage; // StreamingHttpOutputMessage增加的setBody()方法,关于它下面会给一个使用案例~~~~ streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() { // 注意此处复写:返回的是outputStream ,它也是靠我们的writeInternal对它进行写入的~~~~ @Override public OutputStream getBody() { return outputStream; } @Override public HttpHeaders getHeaders() { return headers; } })); } // 最后它执行了flush,这也就是为何我们自己一般不需要flush的原因 else { writeInternal(t, outputMessage); outputMessage.getBody().flush(); } } // 三个抽象方法 protected abstract boolean supports(Class<?> clazz); protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; protected abstract void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
关于StreamingHttpOutputMessage
的使用:
表示允许设置流正文的HTTP输出消息,需要注意的是,此类消息通常不支持getBody()访问
// @since 4.0 public interface StreamingHttpOutputMessage extends HttpOutputMessage { // 设置一个流的正文,提供回调 void setBody(Body body); // 定义可直接写入@link outputstream的主体的协定。 // 通过回调机制间接的访问HttpClient库很有作用 @FunctionalInterface interface Body { // 把当前的这个body写进给定的OutputStream void writeTo(OutputStream outputStream) throws IOException; } }
SourceHttpMessageConverter
处理一些和xml相关的资源,比如DOMSource、SAXSource、SAXSource等等,本文略过.
ResourceHttpMessageConverter
负责读取资源文件和写出资源文件数据
这个在上一篇Spring MVC下载的时候有提到过,它来处理把Resource进行写出去。当然它也可以把body的内容写进到Resource里来。
public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> { // 是否支持读取流信息 private final boolean supportsReadStreaming; // 默认支持所有的MediaType~~~~~ 但是它有个类型匹配,所以值匹配入参/返回类型是Resource类型的 public ResourceHttpMessageConverter() { super(MediaType.ALL); this.supportsReadStreaming = true; } @Override protected boolean supports(Class<?> clazz) { return Resource.class.isAssignableFrom(clazz); } // 直观感受:读的时候也只支持InputStreamResource和ByteArrayResource这两种resource的直接封装 @Override protected Resource readInternal(Class<? extends Resource> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { if (this.supportsReadStreaming && InputStreamResource.class == clazz) { return new InputStreamResource(inputMessage.getBody()) { @Override public String getFilename() { return inputMessage.getHeaders().getContentDisposition().getFilename(); } }; } // 若入参类型是Resource接口,也是当作ByteArrayResource处理的 else if (Resource.class == clazz || ByteArrayResource.class.isAssignableFrom(clazz)) { // 把inputSteeam转换为byte[]数组~~~~~~ byte[] body = StreamUtils.copyToByteArray(inputMessage.getBody()); return new ByteArrayResource(body) { @Override @Nullable public String getFilename() { return inputMessage.getHeaders().getContentDisposition().getFilename(); } }; } else { throw new HttpMessageNotReadableException("Unsupported resource class: " + clazz, inputMessage); } } @Override protected void writeInternal(Resource resource, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { writeContent(resource, outputMessage); } // 写也非常的简单,就是把resource这个资源的内容写到body里面去,此处使用的StreamUtils.copy这个工具方法,专门处理流 // 看到此处我们自己并不需要flush,但是需要自己关闭流 protected void writeContent(Resource resource, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { try { InputStream in = resource.getInputStream(); try { StreamUtils.copy(in, outputMessage.getBody()); } catch (NullPointerException ex) { // ignore, see SPR-13620 } finally { try { in.close(); } catch (Throwable ex) { // ignore, see SPR-12999 } } } catch (FileNotFoundException ex) { // ignore, see SPR-12999 } } }
使用它模拟完成上传功能:上传表单如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>测试FormHttpMessageConverter</title> </head> <body> <!-- 表单的enctype一定要标注成multipart形式,否则是拿不到二进制流的 --> <form action="http://localhost:8080/demo_war_war/upload" method="post" enctype="multipart/form-data"> 用户名 <input type="text" name="userName"> 头像 <input type="file" name="touxiang"> <input type="submit"> </form> </body> </html>
// 模拟使用Resource进行文件的上传~~~ @ResponseBody @RequestMapping(value = "/upload", method = RequestMethod.POST) public String upload(@RequestBody Resource resource) { //此处不能用接口Resource resource dumpStream(resource); return "success"; } // 模拟写文件的操作(此处写到控制台) private static void dumpStream(Resource resource) { InputStream is = null; try { //1.获取文件资源 is = resource.getInputStream(); //2.读取资源 byte[] descBytes = new byte[is.available()]; is.read(descBytes); System.out.println(new String(descBytes, StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); } finally { try { //3.关闭资源 is.close(); } catch (IOException e) { } } }
控制台结果为:
由此可见利用它是可以把客户端的资源信息都拿到的,从而间接的实现文件的上传的功能。
ByteArrayHttpMessageConverter
和上面类似,略
ObjectToStringHttpMessageConverter
它是对StringHttpMessageConverter的一个扩展。它在Spring内部并没有装配进去。若我们需要,可以自己装配到Spring MVC里面去
public class ObjectToStringHttpMessageConverter extends AbstractHttpMessageConverter<Object> { // 我们只需要自定定义这个转换器 让它实现String到Obj之间的互相转换~~~ private final ConversionService conversionService; private final StringHttpMessageConverter stringHttpMessageConverter; ... // 下面省略 // 读的时候先用stringHttpMessageConverter读成String,再用转换器转为Object对象 // 写的时候先用转换器转成String,再用stringHttpMessageConverter写进返回的body里 }
Json相关转换器
可以看到一个是谷歌阵营,一个是jackson阵营。
利用谷歌的Gson进行json序列化的处理~~~
// @since 4.1 课件它被Spring选中的时间还是比较晚的 public class GsonHttpMessageConverter extends AbstractJsonHttpMessageConverter { private Gson gson; public GsonHttpMessageConverter() { this.gson = new Gson(); } // @since 5.0 调用者可以自己指定一个Gson对象了 public GsonHttpMessageConverter(Gson gson) { Assert.notNull(gson, "A Gson instance is required"); this.gson = gson; } // 因为肯定是文本,所以这里使用Reader 没有啥问题 // 父类默认用UTF-8把inputStream转为了更友好的Reader @Override protected Object readInternal(Type resolvedType, Reader reader) throws Exception { return getGson().fromJson(reader, resolvedType); } @Override protected void writeInternal(Object o, @Nullable Type type, Writer writer) throws Exception { // 如果带泛型 这里也是特别的处理了兼容处理~~~~ if (type instanceof ParameterizedType) { getGson().toJson(o, type, writer); } else { getGson().toJson(o, writer); } } // 父类定义了它支持的MediaType类型~ public AbstractJsonHttpMessageConverter() { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); setDefaultCharset(DEFAULT_CHARSET); } }
MappingJackson2HttpMessageConverter
利用亲儿子Jackson进行json序列化(当然,它并不是真正的亲儿子)
// @since 3.1.2 出来可谓最早。正统太子 public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { // 该属性在父类定义~~~ protected ObjectMapper objectMapper; @Nullable private String jsonPrefix; // 支持指定的MediaType类型~~ public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); } // 所有的读、写都在父类AbstractJackson2HttpMessageConverter里统一实现的,稍微有点复杂性 }
总体上看,jackson的实现是最为完善的~~~
备注:Gson和Jackson转换器他俩都是支持jsonPrefix我们可以自定义Json前缀的~~~
若你的返回值是Map、List等,只要MediaType对上了,这种json处理器都是可以处理的。因为他们泛型上都是Object表示入参、 返回值任意类型都可以处理~~~
ProtobufHttpMessageConverter、ProtobufJsonFormatHttpMessageConverter
略
StringHttpMessageConverter
这个是使用得非常广泛的一个消息转换器,专门处理入参/出参字符串类型。
// @since 3.0 出生非常早 public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> { // 这就是为何你return中文的时候会乱码的原因(若你不设置它的编码的话~) public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1; @Nullable private volatile List<Charset> availableCharsets; // 标识是否输出 Response Headers:Accept-Charset(默认true表示输出) private boolean writeAcceptCharset = true; public StringHttpMessageConverter() { this(DEFAULT_CHARSET); } public StringHttpMessageConverter(Charset defaultCharset) { super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL); } //Indicates whether the {@code Accept-Charset} should be written to any outgoing request. // Default is {@code true}. public void setWriteAcceptCharset(boolean writeAcceptCharset) { this.writeAcceptCharset = writeAcceptCharset; } // 只处理String类型~ @Override public boolean supports(Class<?> clazz) { return String.class == clazz; } @Override protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException { // 哪编码的原则为: // 1、contentType自己指定了编码就以指定的为准 // 2、没指定,但是类型是`application/json`,统一按照UTF_8处理 // 3、否则使用默认编码:getDefaultCharset ISO_8859_1 Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType()); // 按照此编码,转换为字符串~~~ return StreamUtils.copyToString(inputMessage.getBody(), charset); } // 显然,ContentLength和编码也是有关的~~~ @Override protected Long getContentLength(String str, @Nullable MediaType contentType) { Charset charset = getContentTypeCharset(contentType); return (long) str.getBytes(charset).length; } @Override protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException { // 默认会给请求设置一个接收的编码格式~~~(若用户不指定,是所有的编码都支持的) if (this.writeAcceptCharset) { outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets()); } // 根据编码把字符串写进去~ Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType()); StreamUtils.copy(str, charset, outputMessage.getBody()); } ... }
我们有可以这么来写,达到我们一定的目的:
// 因为它支持MediaType.TEXT_PLAIN, MediaType.ALL所有类型,所以你的contentType无所谓~~~ 它都能够处理 @ResponseBody @RequestMapping(value = "/test", method = RequestMethod.POST) public String upload(@RequestBody String body) { return "Hello World"; }
这种书写方式它不管是入参,还是返回值处理的转换器,都是用到的StringHttpMessageConverter。用它来接收入参和上面例子Resource有点像,只是StringHttpMessageConverter它只能解析文本内容,而Resource可以处理所有。
需要注意的是:若你的项目中大量使用到了此转换器,请一定要注意编码问题。一般不建议直接使用StringHttpMessageConverter,而是我们配置好编码(UTF-8)后,再把它加入到Spring MVC里面,这样就不会有乱码问题了
另外我们或许看到过有的小伙伴竟这么来写:为了给前端返回一个json串
@ResponseBody @RequestMapping(value = "/test") public String test() { return "{\"status\":0,\"errmsg\":null,\"data\":{\"query\":\"酒店查询\",\"num\":65544,\"url\":\"www.test.com\"}}"; }
虽然这么做结果是没有问题的,但是非常非常的不优雅,属于低级的行为。
通过自己构造Json串的形式(虽然你可能直接借助Fastjson去转,但也很低级),现在看来这么做是低级的、愚蠢的,小伙伴们千万别~~~~这么去做
BufferedImageHttpMessageConverter
处理java.awt.image.BufferedImage,和awt相关。略
GenericHttpMessageConverter 子接口
GenericHttpMessageConverter接口继承自HttpMessageConverter接口,二者都是在org.springframework.http.converter包下。它的特点就是:它处理目标类型为泛型类型的类型~~~
public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T> { //This method should perform the same checks than {@link HttpMessageConverter#canRead(Class, MediaType)} with additional ones related to the generic type. // 它的效果同父接口的canRead,但是它是加了一个泛型类型~~~来加以更加详细的判断 boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType); // 一样也是加了泛型类型 T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; //@since 4.2 boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType); // @since 4.2 void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
可以看出处理Json方面的转换器,都实现了此接口。此处主要以阿里巴巴的FastJson
转换器为例加以说明: