SpringBoot中自定义HttpMessageConverter

2,479 阅读3分钟

- 前言:本文试图以快速,清晰的方式自定义消息转换器,带大家理解springboot中如何将消息转换为需要的格式


1. 使用场景

目前主流的场景是:前端通过调用后端暴漏的HTTP接口,进行访问,大多以JSON格式传参,后端不再经过试图解析器,而是直接将response返回给前端。

@RequestBody,主要用来将请求体中的参数按要求提取,包装成需要的格式。 @ResponseBody,主要将响应体中的参数按要求提取,包装后返回给前端。 这都需要HttpMessageConverter,SpringBoot默认有很多,常用的如下图:

比如用application/json的格式请求,用application/json的格式返回,默认就会使用MappingJacksonHttpMessageConverter image.png

所以,如果想使用XMl格式返回,甚至自己定义了一个格式接收和返回,怎么办呢?

我们只需要去定义一个新的HttpMessageConverter即可。 需要声明几个点:

1、要转换什么类型(Class)的数据

2、支持Content-Type是哪几种类型

3、读取request的代码

4、写入response的代码

上代码:

@Service
//1.实现AbstractHttpMessageConverter
public class ResponseToXlsConverter extends AbstractHttpMessageConverter<AjaxResponse> {
    private static final MediaType EXCEL_TYPE = MediaType.valueOf("application/vnd.ms-excel");

    public ResponseToXlsConverter() {
        //2.声明支持的Content-Type类型
        super(EXCEL_TYPE,MediaType.APPLICATION_JSON);
    }

    //3.声明支持那种类型的转换,也就是范型中的T
    @Override
    protected boolean supports(Class<?> clazz) {
        return (AjaxResponse.class == clazz);
    }

    //4、对request修改,配合@RequestBody可以将请求体中的内容封装,在controllet的参数中获得
    //此代码中我试图接受application/json的请求
    @Override
    protected AjaxResponse readInternal(Class<? extends AjaxResponse> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        System.out.println("readInternal 生效");
        if (clazz == AjaxResponse.class) {
            String s = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
            return AjaxResponse.success(s, "heihei");

        }
        return null;
    }

    //5、对输出修改,配合@ResponseBody,直接跳过视图解析器,目前我返回了一个excel表格
    @Override
    protected void writeInternal(AjaxResponse ajaxResponse, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        System.out.println("writeInternal 生效");
        final Workbook workbook = new HSSFWorkbook();
        final Sheet sheet = workbook.createSheet();
        final Row row = sheet.createRow(0);
        row.createCell(0).setCellValue(ajaxResponse.getMessage());
        row.createCell(1).setCellValue(ajaxResponse.getData().toString());
        workbook.write(outputMessage.getBody());
    }
}

将此转换器加入 Converters即可,

public class MyWebMvcConfigurer implements WebMvcConfigurer {
    @Autowired
    CustomHandlerInterceptor customHandlerInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customHandlerInterceptor).addPathPatterns("/*");
    }


    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    //加入头部,第一个被解析
        converters.add(0, new ResponseToXlsConverter());
    }
}

看下我的Controller: 我试图将json参数,解析成AjaxResponse,当然如果是application/vnd.ms-excel,我也可以解析,因为我的converter支持2种类型的格式。

@GetMapping(value = "/article/{id}")
public AjaxResponse article(@PathVariable Long id, @RequestBody AjaxResponse se) {
    log.info("-->{}", se);
    return AjaxResponse.success("hehe,nimade");
}

debug 看下原因

1、进入到了自定义的Converter中,比较supports方法和canRead方法

image.png

2、support肯定是true的,我们看下canRead, mediaType就是前端请求头中的Content-Type, 我们的supportsMediaTypes中支持2个,包含了json,就会成功。

image.png

3、检查请求体的内容,马上就会进入我们自己的readInternal方法, 没有请求体的请求方式是无法进行解析的,比如form-data。 我们的readInternal返回了AjaxResponse,赋予Controller中。

image.png

4、其实请求的解析 MappingJacksonHttpMessageConverter也可以做到,但是我们将自己的声明在List的第一位,所以先用自己的。

4、进行响应是流程一模一样

变化

1、converter如果有多个命中,那么会先用第一个,所以我们的add(0,converter)将自己的放在了第一位。

2、请求的content-type是由请求头带来的,但是响应时,可以通过controller中@RequestMapping的 produces来指定想要返回的类型,也就会影响到debug中的mediaType,所以比如说你想返回String,可以指定一下,就不会用自己的转换器了。