环境:SpringBoot2.7.12
1. 简介
在Spring MVC中,HttpMessageConverter主要用于将HTTP请求的输入内容转换为指定的Java对象,以及将Java对象转换为HTTP响应的输出内容。这种灵活的消息转换机制就是利用HttpMessageConverter来实现的。
Spring MVC提供了多个默认的HttpMessageConverter实现,包括处理JSON、XML、文本等格式的Converter。另外,我们也可以自定义HttpMessageConverter来处理其他格式的数据。
Spring MVC提供了两个注解:@RequestBody和@ResponseBody,分别用于完成请求报文到对象和对象到响应报文的转换。
然而,有时候默认的HttpMessageConverter无法满足特定的需求,例如,当我们需要处理的数据格式没有默认的Converter时,或者我们需要对现有的Converter进行扩展时,就需要自定义HttpMessageConverter。
自定义HttpMessageConverter可以让我们更加灵活地控制数据转换的过程,例如我们可以自定义转换规则、异常处理等。
接下来我们通过一个实例讲解如何自定义HttpMessageConverter。
需求
接口请求数据格式:
xxx|yyy|zzz|...
接口返回JSON数据格式
{
"xxx": xxx,
"yyy": yyy,
"zzz": zzz,
...
}
其实就上面的数据格式,我们完全可以不用自定义HttpMessageConverter也是完全可以实现的。我们这里主要就是教大家如何在特殊的需求下实现特定的数据转换处理。
2. 实战案例
自定义HttpMessageConverter转换器
public class PackHttpMessageConverter implements HttpMessageConverter<Object> {
// 设置自定义的Content-Type类型,这样就限定了只有请求的内容类型是该类型才会使用该转换器进行处理
private static final MediaType PACK = new MediaType("application", "pack", StandardCharsets.UTF_8) ;
// 判断当前转换器是否能够读取数据
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return PACK.equals(mediaType) ;
}
// 判断当前转换器是否可以将结果数据进行输出到客户端
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return true ;
}
// 返回当前转换器只支持application/pack类型的数据格式
@Override
public List<MediaType> getSupportedMediaTypes() {
return Arrays.asList(PACK) ;
}
// 从请求中读取数据
@Override
public Object read(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
InputStream is = inputMessage.getBody() ;
String res = IOUtils.toString(is, StandardCharsets.UTF_8) ;
// 这里简单处理只针对Users类型的对象处理
if (clazz == Users.class) {
try {
// 创建实例
Users target = (Users) clazz.newInstance() ;
String[] s = res.split("\\|");
target.setId(Long.valueOf(s[0])) ;
target.setName(s[1]) ;
target.setAge(Integer.valueOf(s[2])) ;
target.setIdNo(s[3]) ;
return target ;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace() ;
}
}
return null ;
}
// 将Controller方法返回值写到客户端
@Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// 设置响应头为json格式
outputMessage.getHeaders().add("Content-Type", "application/json;charset=UTF-8") ;
ObjectMapper mapper = new ObjectMapper() ;
OutputStream os = outputMessage.getBody();
// 输出结果内容
os.write(mapper.writeValueAsString(t).getBytes(StandardCharsets.UTF_8)) ;
os.flush();
}
}
将PackHttpMessageConverter注册到容器中
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PackHttpMessageConverter()) ;
}
}
到这里自定义HttpMessageConverter及注册到容器中就全部完成了,开发还是比较简单,接下来做测试
接口
// 方法非常简单还是用的那些常用的类,@RequestBody接收请求body中的内容
@PostMapping("/i")
public Object i(@RequestBody Users user) {
System.out.println(handlerAdapter) ;
return user ;
}
通过Postman测试接口
设置请求的header
图片
图片
似乎没有任何的问题,其实你只要在写的方法中打印下日志,或者调试下,你会发现你的write方法根本就没有被调用,也就是说写数据并没有使用到我们自定义的实现,这是因为有优先级比我们自定义的转换器高,所以要想让写消息也调用自定义的。我们需要如下修改注册方式:
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new PackHttpMessageConverter()) ;
}
这样我们自定义的转换器就排到了第一的位置,这样就会调用我们自定义的write方法。
以上就是自定义HttpMessageConverter全部内容。
3. 实现原理
请求参数由于添加了@RequestBody,所以方法的参数解析器使用的是RequestResponseBodyMethodProcessor。
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// ...
// 读取请求数据;调用父类方法
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
// ...
}
}
AbstractMessageConverterMethodArgumentResolver
public abstract class AbstractMessageConverterMethodArgumentResolver {
protected <T> Object readWithMessageConverters(...) {
// ...
// 遍历所有的消息转换器
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
// 判断当前转换器是否读,也就上面我们自定义中实现的canRead方法
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// 读取具体的数据内容
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
}
原理也比较的简单。
自定义HttpMessageConverter是Spring MVC中一个强大的工具,它可以帮助开发者更加灵活地控制数据转换的过程,满足特定的需求。