自定义消息转换器

浏览:1547 发布日期:2021-07-05 07:29:36

自定义消息转换器

消息转换器的核心接口是HttpMessageConverter<T>

public interface HttpMessageConverter<T> {

    // 指示此转换器是否可以读取给定的类和媒体类型。 
    boolean canRead(Class<?> clazz, MediaType mediaType);

    // 指示此转换器是否可以写入给定的类和媒体类型。 
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    // 返回此转换器支持的 MediaType 对象列表。 
    List<MediaType> getSupportedMediaTypes();

    // 从给定的输入消息中读取给定类型的对象,并返回它 
    T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

    // 将给定的对象写入给定的输出消息
    void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;

}

消息转换器依赖MediaTypeClass<?>来选择消息转换器处理被@ResponseBody@RequestBody修饰的对象。

AbstractHttpMessageConverter<T>

大多数HttpMessageConverter 实现的抽象基类。 这个基类通过supportedMediaTypes bean 属性添加了对设置支持的 MediaTypes 的支持。 在写入输出消息时,它还增加了对 Content-Type 和 Content-Length 的支持。

我们的自定义消息转换器也将继承这个类:

public class ExcelMessageConverter extends AbstractHttpMessageConverter<Object>
{
    private static Logger logger = LoggerFactory.getLogger(ExcelMessageConverter.class);

    public ExcelMessageConverter()
    {
        MediaType mediaType = new MediaType("application",
                "vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        setSupportedMediaTypes(Collections.singletonList(mediaType));
    }

    @Override
    protected boolean supports(Class<?> clazz)
    {
        if (! List.class.isAssignableFrom(clazz))
        {
            logger.info("excel消息转换器只支持List或其子类对象");
            return false;
        }
        else
        {
            return true;
        }
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType)
    {
        return super.canWrite(clazz, mediaType);
    }

    @Override
    protected boolean canWrite(MediaType mediaType)
    {
        return super.canWrite(mediaType);
    }

    @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException
    {
        return null;
    }

    @Override
    protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException
    {
        logger.info("当前类为:" + obj.getClass());
        List list = (List) obj;
        XSSFWorkbook excelWork = ExcelUtil.toExcel(list);

        OutputStream os = outputMessage.getBody();
        excelWork.write(os);
    }
}

我们这个转换器将List对象生成.xlsx下载文件,它检查类的类型,只处理List类型。ExcelUtil.toExcel方法如下:

public static XSSFWorkbook toExcel(List objList)
{
        if (CollectionUtils.isEmpty(objList))
        {
            throw new NullPointerException("无效的数据");
        }

        Class aClass = objList.get(0).getClass();
        List<Field> fields = Arrays.stream(aClass.getDeclaredFields())
                .filter(f -> f.getAnnotation(ExcelField.class) != null).collect(Collectors.toList());
        XSSFWorkbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("Sheet1");
        if (fields.size() == 0)
        {
            return workbook;
        }

        for (int i = 0; i < objList.size(); i++)
        {
            Row row = sheet.createRow(i);
            for (int j = 0; j < fields.size(); j++)
            {
                Field field = fields.get(j);
                Cell cell = row.createCell(j);
                ExcelField excelField = field.getAnnotation(ExcelField.class);
                if (i == 0)
                {
                    cell.setCellValue(excelField.name());
                }
                else
                {
                    try
                    {
                        field.setAccessible(true);
                        Object fieldValue = field.get(objList.get(i));
                        cell.setCellValue(fieldValue.toString());
                    }
                    catch (IllegalAccessException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        }

        return workbook;
}

它将List对象转化为XSSFWorkbook对象,以便在消息转换器中输出到浏览器的OutputStream中。

我们希望后端可以根据扩展名来选择这个消息转换器,例如test1.xlsx,那么我们需要过滤器,将.xlsx后缀名的请求,修改HTTP Accept请求头。

HeaderMapHttpRequest headerRequest = new HeaderMapHttpRequest(request);
String extensionStr = request.getRequestURI();
if (extensionStr.endsWith(".xlsx"))
{
    headerRequest.putHeader("Accept",
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    logger.info("为xlsx请求,修改Accept");
}
...

HeaderMapHttpRequest,它继承自HttpServletRequestWrapper,用于提供修改请求头功能。