消息转换器的核心接口是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,用于提供修改请求头功能。