消息转换器的核心接口是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; }
消息转换器依赖MediaType
和Class<?>
来选择消息转换器处理被@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,用于提供修改请求头功能。