一、介绍
EnhancedExportExcelUtil 是对基础 ExportExcelUtil 的扩展封装,核心目标是简化 Excel 导出流程:通过反射 + 注解实现表头自动生成,直接对接 HTTP 响应完成浏览器下载,同时复用原有导出逻辑,兼顾易用性与兼容性。适用于 Web 项目中高频的 Excel 导出场景(如报表下载、数据批量导出)。
二、准备
1.引入依赖
xml
<!-- Excel 2007+ (.xlsx) 支持 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- Excel 97-2003 (.xls) 支持(可选,若需兼容旧格式) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
2.注解定义
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExportFieldMeta {
String title(); // 表头标题
int order() default 0; // 列顺序
}
数据实体示例
业务实体类通过注解标记导出字段:
java
public class User {
@ExportFieldMeta(title = "用户ID", order = 1)
private Long id;
@ExportFieldMeta(title = "用户名", order = 2)
private String username;
@ExportFieldMeta(title = "手机号", order = 3)
private String phone;
// getter/setter 省略
}
三、实现
java
import org.apache.poi.ss.usermodel.Workbook;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* 增强版 Excel 导出工具类(扩展 ExportExcelUtil)
*/
public class EnhancedExportExcelUtil extends ExportExcelUtil {
/**
* 基于数据集反射导出(自动生成表头)
* @param response HTTP 响应对象
* @param dataList 导出数据集
* @param fileName 下载文件名
* @param <T> 数据类型
*/
public static <T> void export(HttpServletResponse response, List<T> dataList, String fileName) {
if (dataList == null || dataList.isEmpty()) {
throw new IllegalArgumentException("导出数据集不能为空");
}
// 通过反射获取表头(基于 ExportFieldMeta 注解)
Class<?> clazz = dataList.get(0).getClass();
String[] headers = getHeadersByAnnotation(clazz);
// 调用父类核心方法生成 Workbook
Workbook workbook = exportExcel(headers, dataList);
// 写入 HTTP 响应
writeToResponse(response, workbook, fileName);
}
/**
* 基于类反射导出(空数据模板)
* @param response HTTP 响应对象
* @param clazz 数据类
* @param fileName 下载文件名
*/
public static void exportTemplate(HttpServletResponse response, Class<?> clazz, String fileName) {
String[] headers = getHeadersByAnnotation(clazz);
Workbook workbook = exportExcel(headers, new ArrayList<>()); // 空数据模板
writeToResponse(response, workbook, fileName);
}
/**
* 反射获取表头(基于 ExportFieldMeta 注解)
*/
private static String[] getHeadersByAnnotation(Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();
// 筛选带注解的字段并按 order 排序
List<Field> annotatedFields = new ArrayList<>();
for (Field field : fields) {
if (field.isAnnotationPresent(ExportFieldMeta.class)) {
annotatedFields.add(field);
}
}
annotatedFields.sort(Comparator.comparingInt(f -> f.getAnnotation(ExportFieldMeta.class).order()));
// 提取表头标题
return annotatedFields.stream()
.map(f -> f.getAnnotation(ExportFieldMeta.class).title())
.toArray(String[]::new);
}
/**
* 将 Workbook 写入 HTTP 响应(实现浏览器下载)
*/
private static void writeToResponse(HttpServletResponse response, Workbook workbook, String fileName) {
try {
// 设置响应头(解决中文文件名乱码)
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));
// 写入输出流
OutputStream outputStream = response.getOutputStream();
workbook.write(outputStream);
// 关闭资源
outputStream.flush();
outputStream.close();
workbook.close();
} catch (Exception e) {
throw new RuntimeException("Excel 导出失败", e);
}
}
}
四、说明
- 核心方法解析
export(HttpServletResponse, List<T>, String):主流场景,传入数据集自动生成带数据的 Excel 并下载;exportTemplate(HttpServletResponse, Class<?>, String):生成空数据模板(仅表头),适用于用户下载模板后填写上传;getHeadersByAnnotation(Class<?>):通过反射扫描ExportFieldMeta注解,提取表头标题并按order排序;writeToResponse(...):封装 HTTP 响应配置( contentType、文件名编码),完成文件下载流写入。
- 继承复用逻辑 父类
ExportExcelUtil需提供exportExcel(String[] headers, List<T> dataList)方法,负责将表头和数据写入Workbook,子类通过继承直接复用该核心能力,无需重复开发 Excel 写入逻辑。
五、补充
- 注解扩展 可在
ExportFieldMeta中增加更多属性(如width列宽、format数据格式),进一步定制 Excel 样式:
java
@ExportFieldMeta(title = "创建时间", order = 4, format = "yyyy-MM-dd", width = 20)
private Date createTime;
- 异常处理实际使用中需捕获反射异常(如无注解字段)、IO 异常(响应流写入失败),返回友好提示(如 "导出失败,请重试")。
- 格式兼容 若需支持
.xls格式,可在writeToResponse中根据文件后缀切换contentType:
java
if (fileName.endsWith(".xls")) {
response.setContentType("application/vnd.ms-excel");
}
六、扩展
动态表头支持结合数据库配置表头(如用户自定义导出字段),替换注解方式,实现更灵活的表头生成:
java
// 从数据库读取用户配置的表头
List<String> customHeaders = headerConfigService.getHeadersByUser(userId);
大数据量导出 集成 SXSSFWorkbook(流式导出),避免内存溢出:
java
// 父类中替换为 SXSSFWorkbook
Workbook workbook = new SXSSFWorkbook(100); // 内存缓存100行
多 Sheet 导出
扩展方法支持多数据集分 Sheet 导出:
java
public static void exportMultiSheet(HttpServletResponse response, Map<String, List<?>> sheetDataMap, String fileName) {
Workbook workbook = new XSSFWorkbook();
for (Map.Entry<String, List<?>> entry : sheetDataMap.entrySet()) {
String sheetName = entry.getKey();
List<?> dataList = entry.getValue();
// 每个 Sheet 单独生成表头和数据
// ...
}
writeToResponse(response, workbook, fileName);
}
七、小知识
- 反射性能优化 :频繁导出时可缓存反射获取的表头信息(如用
ConcurrentHashMap存储类与表头的映射),避免重复反射解析。 - 文件名编码 :不同浏览器对文件名编码支持不同,
URLEncoder兼容性最优,也可使用new String(fileName.getBytes("GBK"), "ISO-8859-1")适配 IE 浏览器。 - 响应头设置 :添加
response.setHeader("Pragma", "no-cache")和response.setHeader("Cache-Control", "no-cache"),避免浏览器缓存下载文件。
八、总结
EnhancedExportExcelUtil 通过注解 + 反射 简化表头配置,HTTP 响应封装 实现一键下载,继承复用保证核心逻辑稳定,大幅降低 Web 项目中 Excel 导出的开发成本。其设计思路兼顾 "易用性" 与 "扩展性",既满足常规导出需求,也支持模板生成、大数据量导出等进阶场景,是项目中高效的 Excel 导出解决方案。