前文通过阅读源码,深入分析了DispatcherServlet及相关组件的工作流程,本文不再阅读源码,介绍一下扩展HttpMessageConverter的方式。
HttpMessageConverter工作方式及扩展方式
前文介绍过,HttpMessageConverter是读写请求体和响应体的组件。
RequestResponseBodyMethodProcessor(用于解析请求参数、处理返回值)从内置的HttpMessageConverter查找支持当前请求体、响应体的实例,然后调用read、write来读写数据。
Spring内置的HttpMessageConverter在装配RequestResponseBodyMethodProcessor的时候创建,具体代码在WebMvcConfigurationSupport类addDefaultHttpMessageConverters方法中。
开发者如果要扩展使用自己的HttpMessageConverter实现,可以编写组件实现WebMvcConfigurer接口,在extendMessageConverters方法中注入自己的HttpMessageConverter实现类对象。
自定义HttpMessageConverter
需求描述
系统需要对响应体进行加密、对请求体解密操作。
思路:
- 编写类实现HttpMessageConverter接口,read方法中先对请求体解密,之后在做json反序列化
- write方法先做json序列化,之后再加密
- 通过WebMvcConfigurer注册
编写HttpMessageConverter实现类
public class MyMappingJackson2HttpMessageConverter implements GenericHttpMessageConverter<Object> {
private static final String S_KEY = "1234567890123456";
private static final String IV_PARAMETER = "abcdefghijklmnop";
// 用来做json序列化和反序列化,自己编写代码也可以,此处直接使用MappingJackson2HttpMessageConverter来做
private final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter;
// 读写字符串
private final StringHttpMessageConverter stringHttpMessageConverter;
public MyMappingJackson2HttpMessageConverter(
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) {
this.jackson2HttpMessageConverter = jackson2HttpMessageConverter;
this.stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
}
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return jackson2HttpMessageConverter.canRead(clazz, mediaType);
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return jackson2HttpMessageConverter.canWrite(clazz, mediaType);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return jackson2HttpMessageConverter.getSupportedMediaTypes();
}
@Override
public Object read(Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// 读取请求原始字节
byte[] bytes = readBytes(inputMessage);
// 解密
byte[] decryptBytes = AesUtil.decrypt(bytes, S_KEY, IV_PARAMETER);
// 封装HttpInputMessage供下面反序列化使用
HttpInputMessage in = new MyHttpInputMessage(inputMessage, decryptBytes);
// json反序列化
return jackson2HttpMessageConverter.read(clazz, in);
}
@Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// json序列化
jackson2HttpMessageConverter.write(
o, contentType, new MyHttpOutputMessage(outputMessage, byteArrayOutputStream));
byte[] bytes = byteArrayOutputStream.toByteArray();
// 加密
byte[] encryptStr = AesUtil.encrypt(bytes, S_KEY, IV_PARAMETER);
// 将响应写出去
this.stringHttpMessageConverter.write(
new String(encryptStr, StandardCharsets.UTF_8), contentType, outputMessage);
}
@Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
return jackson2HttpMessageConverter.canRead(type, contextClass, mediaType);
}
@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
byte[] bytes = readBytes(inputMessage);
byte[] decryptBytes = AesUtil.decrypt(bytes, S_KEY, IV_PARAMETER);
HttpInputMessage in = new MyHttpInputMessage(inputMessage, decryptBytes);
return jackson2HttpMessageConverter.read(type, contextClass, in);
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return jackson2HttpMessageConverter.canWrite(type, clazz, mediaType);
}
@Override
public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
jackson2HttpMessageConverter.write(
o, type, contentType, new MyHttpOutputMessage(outputMessage, byteArrayOutputStream));
byte[] bytes = byteArrayOutputStream.toByteArray();
byte[] encryptStr = AesUtil.encrypt(bytes, S_KEY, IV_PARAMETER);
this.stringHttpMessageConverter.write(
new String(encryptStr, StandardCharsets.UTF_8), contentType, outputMessage);
}
private byte[] readBytes(HttpInputMessage inputMessage) throws IOException {
long contentLength = inputMessage.getHeaders().getContentLength();
ByteArrayOutputStream bos =
new ByteArrayOutputStream(
contentLength >= 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE);
StreamUtils.copy(inputMessage.getBody(), bos);
return bos.toByteArray();
}
private static class MyHttpInputMessage implements HttpInputMessage {
private final HttpInputMessage originalHttpInputMessage;
private final byte[] buf;
public MyHttpInputMessage(HttpInputMessage originalHttpInputMessage, byte[] buf) {
this.originalHttpInputMessage = originalHttpInputMessage;
this.buf = buf;
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(this.buf);
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = this.originalHttpInputMessage.getHeaders();
headers.setContentLength(this.buf.length);
return headers;
}
}
private static class MyHttpOutputMessage implements HttpOutputMessage {
private final HttpOutputMessage originalHttpOutputMessage;
private final OutputStream outputStream;
public MyHttpOutputMessage(HttpOutputMessage originalHttpOutputMessage,
OutputStream outputStream) {
this.originalHttpOutputMessage = originalHttpOutputMessage;
this.outputStream = outputStream;
}
@Override
public OutputStream getBody() throws IOException {
return this.outputStream;
}
@Override
public HttpHeaders getHeaders() {
return this.originalHttpOutputMessage.getHeaders();
}
}
}
注入HttpMessageConverter实现类对象
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = null;
for (HttpMessageConverter<?> messageConverter : converters) {
if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
converter = (MappingJackson2HttpMessageConverter) messageConverter;
break;
}
}
if (converter != null) {
// 注入MyMappingJackson2HttpMessageConverter
MyMappingJackson2HttpMessageConverter myMappingJackson2HttpMessageConverter =
new MyMappingJackson2HttpMessageConverter(converter);
converters.add(0, myMappingJackson2HttpMessageConverter);
}
}
}