詳解自定義SpringMVC的Http信息轉(zhuǎn)換器的使用
在SpringMVC中,可以使用@RequestBody和@ResponseBody兩個注解,分別完成請求報文到對象和對象到響應報文的轉(zhuǎn)換,底層這種靈活的消息轉(zhuǎn)換機制。使用系統(tǒng)默認配置的HttpMessageConverter進行解析,然后把相應的數(shù)據(jù)綁定到要返回的對象上。
HttpInputMessage
這個類是SpringMVC內(nèi)部對一次Http請求報文的抽象,在HttpMessageConverter的read()方法中,有一個HttpInputMessage的形參,它正是SpringMVC的消息轉(zhuǎn)換器所作用的受體“請求消息”的內(nèi)部抽象,消息轉(zhuǎn)換器從“請求消息”中按照規(guī)則提取消息,轉(zhuǎn)換為方法形參中聲明的對象。
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的消息轉(zhuǎn)換器所作用的受體“響應消息”的內(nèi)部抽象,消息轉(zhuǎn)換器將“響應消息”按照一定的規(guī)則寫到響應報文中。
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 :判斷該轉(zhuǎn)換器是否能將請求內(nèi)容轉(zhuǎn)換成Java對象
- canWrite :判斷該轉(zhuǎn)換器是否可以將Java對象轉(zhuǎn)換成返回內(nèi)容
- getSupportedMediaTypes :獲得該轉(zhuǎn)換器支持的MediaType類型
- read :讀取請求內(nèi)容并轉(zhuǎn)換成Java對象
- write :將Java對象轉(zhuǎn)換后寫入返回內(nèi)容
其中 read 和 write 方法的參數(shù)分別有有 HttpInputMessage 和 HttpOutputMessage 對象,這兩個對象分別代表著一次Http通訊中的請求和響應部分,可以通過 getBody 方法獲得對應的輸入流和輸出流。
當前Spring中已經(jīng)默認提供了相當多的轉(zhuǎn)換器,分別有:
| 名稱 | 作用 | 讀支持MediaType | 寫支持MediaType |
|---|---|---|---|
| ByteArrayHttpMessageConverter | 數(shù)據(jù)與字節(jié)數(shù)組的相互轉(zhuǎn)換 | / | application/octet-stream |
| StringHttpMessageConverter | 數(shù)據(jù)與String類型的相互轉(zhuǎn)換 | text/* | text/plain |
| FormHttpMessageConverter | 表單與MultiValueMap<string, string=””>的相互轉(zhuǎn)換 | application/x-www-form-urlencoded | application/x-www-form-urlencoded |
| SourceHttpMessageConverter | 數(shù)據(jù)與javax.xml.transform.Source的相互轉(zhuǎn)換 | text/xml和application/xml | text/xml和application/xml |
| MarshallingHttpMessageConverter | 使用SpringMarshaller/Unmarshaller轉(zhuǎn)換XML數(shù)據(jù) | text/xml和application/xml | text/xml和application/xml |
| MappingJackson2HttpMessageConverter | 使用Jackson的ObjectMapper轉(zhuǎn)換Json數(shù)據(jù) | application/json | application/json |
| MappingJackson2XmlHttpMessageConverter | 使用Jackson的XmlMapper轉(zhuǎn)換XML數(shù)據(jù) | application/xml | application/xml |
| BufferedImageHttpMessageConverter | 數(shù)據(jù)與java.awt.image.BufferedImage的相互轉(zhuǎn)換 | Java I/O API支持的所有類型 | Java I/O API支持的所有類型 |
HttpMessageConverter匹配過程:
@RequestBody注解時: 根據(jù)Request對象header部分的Content-Type類型,逐一匹配合適的HttpMessageConverter來讀取數(shù)據(jù)。
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注解時:根據(jù)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轉(zhuǎn)換器
class CustomJsonHttpMessageConverter implements HttpMessageConverter {
//Jackson的Json映射類
private ObjectMapper mapper = new ObjectMapper();
//該轉(zhuǎn)換器的支持類型:application/json
private List supportedMediaTypes = Arrays.asList(MediaType.APPLICATION_JSON);
/**
* 判斷轉(zhuǎn)換器是否可以將輸入內(nèi)容轉(zhuǎn)換成Java類型
* @param clazz 需要轉(zhuǎn)換的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;
}
/**
* 判斷轉(zhuǎn)換器是否可以將Java類型轉(zhuǎn)換成指定輸出內(nèi)容
* @param clazz 需要轉(zhuǎn)換的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;
}
/**
* 獲得該轉(zhuǎn)換器支持的MediaType
* @return
*/
@Override
public List getSupportedMediaTypes() {
return supportedMediaTypes;
}
/**
* 讀取請求內(nèi)容,將其中的Json轉(zhuǎn)換成Java對象
* @param clazz 需要轉(zhuǎn)換的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對象轉(zhuǎn)換成Json返回內(nèi)容
* @param o 需要轉(zhuǎn)換的對象
* @param contentType 返回類型
* @param outputMessage 回執(zhí)對象
* @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 方法向返回結果的輸出流中寫入數(shù)據(jù),所以只需要重寫該方法即可:
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
//重寫writeInternal方法,在返回內(nèi)容前首先進行加密
@Override
protected void writeInternal(Object object,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
//使用Jackson的ObjectMapper將Java對象轉(zhuǎn)換成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());
}
};
}
在這之后還需要將這個自定義的轉(zhuǎn)換器配置到Spring中,這里通過重寫 WebMvcConfigurer 中的 configureMessageConverters 方法添加自定義轉(zhuǎn)換器:
//添加自定義轉(zhuǎn)換器
@Override
public void configureMessageConverters(List<httpmessageconverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Java運行時環(huán)境之ClassLoader類加載機制詳解
這篇文章主要給大家介紹了關于Java運行時環(huán)境之ClassLoader類加載機制的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-01-01
MyBatis-Plus Sequence主鍵的實現(xiàn)
這篇文章主要介紹了MyBatis-Plus Sequence主鍵的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12
基于SSM+Shiro+Bootstrap實現(xiàn)用戶權限管理系統(tǒng)
這篇文章主要介紹了基于SSM+Shiro實現(xiàn)一個用戶權限管理系統(tǒng),每位用戶只可訪問指定的頁面,文中的示例代碼講解詳細,對我們學習或工作有一定幫助,快跟隨小編一起學習吧2021-12-12
自定義spring mvc的json視圖實現(xiàn)思路解析
這篇文章主要介紹了自定義spring mvc的json視圖的實現(xiàn)思路解析,本文給大家介紹的非常詳細,具有參考借鑒價值,需要的朋友可以參考下2017-12-12
Java實現(xiàn)跳躍表(skiplist)的簡單實例
這篇文章主要介紹了Java編程中跳躍表的概念和實現(xiàn)原理,并簡要敘述了它的結構,具有一定參考價值,需要的朋友可以了解下。2017-09-09
Java中static和static?final的區(qū)別詳解
這篇文章主要介紹了Java中static和static?final的區(qū)別詳解,開發(fā)時我們經(jīng)常用到static以及static?final來修飾我們的字段變量,那么他們到底有什么區(qū)別呢?其實他們的區(qū)別可以用使用字節(jié)碼文件來解析,需要的朋友可以參考下2023-10-10

