大家工作中,往往会遇到Excel导出的问题,但是写导出数据的时候,需要构造很多不需要的参数,如Title,列名,还有数据列表。不知道小伙伴们有没有感觉很繁琐,小编自己觉得很繁琐,所以想有一个能够不用 构造这么多东西去写Excel导出,能不能直接给我传递一个集合,我就能直接给导出呢?
针对这个小编也是想了很久终于找到了一套方案,最近也是写了一个初级版本。这里分享给大家,希望能够对那些觉得Excel导出操作繁琐的小伙伴有所帮助。
Jakarta POI 是一套用于访问微软格式文档的Java API。Jakarta POI有很多组件组成,其中有用于操作Excel格式文件的HSSF和用于操作Word的HWPF,在各种组件中目前只有用于操作Excel的HSSF相对成熟。
官方主页http://poi.apache.org/index.html,
API文档http://poi.apache.org/apidocs/index.html
我的功能是使用的POI来完成的Excel导出的,所以这个依赖包是必须有的。
HSSF(用于操作Excel的组件)提供给用户使用的对象在rg.apache.poi.hssf.usermodel包中,主要部分包括Excel对象,样式和格式,有以下几种常用的对象:
基本操作其实和我们写一个Excel一样的。
这就是我们生成一个Excel的基本操作。
工具类代码:
public class ExcelUtil {
/**
* 导出Excel
* @param sheetName sheet名称
* @param title 标题
* @param values 内容
* @param wb HSSFWorkbook对象
* @return
*/
public static HSSFWorkbook getHSSFWorkbook(String sheetName,String []title,String [][]values, HSSFWorkbook wb){
// 第一步,创建一个HSSFWorkbook,对应一个Excel文件
if(wb == null){
wb = new HSSFWorkbook();
}
// 第二步,在workbook中添加一个sheet,对应Excel文件中的sheet
HSSFSheet sheet = wb.createSheet(sheetName);
// 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制
HSSFRow row = sheet.createRow(0);
// 第四步,创建单元格,并设置值表头 设置表头居中
HSSFCellStyle style = wb.createCellStyle();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 创建一个居中格式
//声明列对象
HSSFCell cell = null;
//创建标题
for(int i=0;i<title.length;i++){
cell = row.createCell(i);
cell.setCellValue(title[i]);
cell.setCellStyle(style);
}
//创建内容
for(int i=0;i<values.length;i++){
row = sheet.createRow(i + 1);
for(int j=0;j<values[i].length;j++){
//将内容按顺序赋给对应的列对象
row.createCell(j).setCellValue(values[i][j]);
}
}
return wb;
}
}
我们今天说的不是这个,而是在这个基本操作上再次进行封装。得到一个操作简便的ExcelUtil。
Controller:
@GetMapping("uploadSwopScoreStatement")
public void uploadSwopScoreStatement(HttpServletResponse response, @NotNull Long userId,
Date startTime, Date endTime) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
String dateStr = formatter.format(new Date());
//excel文件名
String fileName = "积分流动报表_" + dateStr + ".xls";
//调用Excel实现得到Excel
HSSFWorkbook wb = statementService.swopScoreStatement(userId, startTime, endTime);
//把文件输出流放到Response中
try {
this.setResponseHeader(response, fileName);
OutputStream os = response.getOutputStream();
wb.write(os);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Service实现:
public HSSFWorkbook swopScoreStatement(Long userId, Date startTime, Date endTime) {
List<ObjId> objIds = sysUserMapper.getIdObj(userId);
List<Long> userIds = powerControlService.getIdsByTree(objIds);
userIds.add(userId);//添加自己的用户id
//获取数据列表
List<TradeFlowExcel> statement = orderMapper.selectAndTradeFlowDetail(userIds,startTime,endTime);
//生成Excel对象(根据数据列表生成Excel)
return ExcelGenerator.genExcel(statement);
}
大家应该看到了,这些操作很简单但是怎么实现的呢 ?带大家看一下。
entity:
@Data
@Sheet(name = "积分变动概况统计") //给Sheet起名字
@Title(value = "积分变动概况统计") //给导出的列表起一个Title
public class TradeFlowExcel {
private Long userId;
@Cell("用户名") //列名
private String nickName;
@Cell("用户账号")
private String account;
@Cell("流水变动情况")
private String tradeAmount;
@Sheet(name = "用户【{}】明细流水",field = "account") //级联查询的话,可以使用这个一口气生成多个Sheet。
List<TradeFlowDetailExcel> tradeFlowDetailExcels;
private Date startTime;
private Date endTime;
}
@Data
@Title(value = "积分流水明细")
public class TradeFlowDetailExcel {
@Cell("用户名")
private String nickName;
@Cell("用户账号")
private String account;
@Cell("流水类型")
private String busiType;
@Cell("收支类型")
private String tradeType;
@Cell("交易金额")
private String tradeAmount;
}
这样导出就 直接有列和Title还有Sheet啦。
下面我们看一下代码实现。
此工具类 是基于反射和注解来实现的。
用到的注解分别有@Cell、@Title、@Sheet 后期还会加入@Font、@Style 控制字体和控制样式。
目前还没有Group分组,Group分组应该会在第二个版本出。
@Cell:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cell {
/**
* 列名(表头)
* @return
*/
String value() default "";
}
@Sheet:
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sheet {
String name() default "sheet";
String [] value() default {};
String [] field() default {};
}
@Title:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Title {
/**
* Excel表明
* @return
*/
String value() default "数据列表";
}
工具类:
public class ExcelGenerator<T> {
/**
* 生成Excel文件对象: 1.创建一个HSSFWorkbook,对应一个Excel文件
* 2.第二步,在workbook中添加一个sheet,对应Excel文件中的sheet 3.在sheet中添加表头第0行,创建单元格,并设置值表头
* 设置表头居中,注意老版本poi对Excel的行数列数有限制 4.插入数据
*
* @param data
* 需要生成Excel的数据列表
* @param <T>
* @return
*/
public static <T> HSSFWorkbook genExcel(List<T> data) {
// 第一步,创建一个HSSFWorkbook,对应一个Excel文件
HSSFWorkbook wb = new HSSFWorkbook();
// 第二步,在workbook中添加一个sheet,对应Excel文件中的sheet
Map<HSSFSheet, List> sheetMap = genSheet(wb, data);
for (HSSFSheet sheet : sheetMap.keySet()) {
// 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制
genTitle(sheet, sheetMap.get(sheet), wb);
// 第四步,插入数据
insertDate(sheet, sheetMap.get(sheet));
}
return wb;
}
/**
* 生成Sheet
*
* @param wb
* @param datas
* @param <T>
*/
private static <T> Map<HSSFSheet, List> genSheet(HSSFWorkbook wb, List<T> datas) {
Map<HSSFSheet, List> res = new HashMap<>();// 结果集合
// 遍历数据列表解析data中的Sheet注解,并获取名称和对应的data列表
if (datas == null || datas.size() == 0) {
throw new RuntimeException("没有数据,无法导出Excel");
}
Object data = datas.get(0);
Class clazz = data.getClass();
Sheet sheetAnnotation = (Sheet) clazz.getAnnotation(Sheet.class);
String sheetName;
SheetAnnotationResolver<Sheet> sheetAnnotationResolver = new SheetAnnotationResolver();
if (sheetAnnotation == null) {
sheetName = "";
} else {
// 获取SheetName数据
sheetName = (String) sheetAnnotationResolver.resolver(sheetAnnotation);
}
HSSFSheet sheetParent = wb.createSheet(sheetName);
res.put(sheetParent, datas);
// 获取获取属性 @Sheet 注解,获取并解析
// 获取所有的field
Field[] fields = clazz.getDeclaredFields();
int index = 0;
for (Field field : fields) {// 遍历data里面的所有字段,判断 数据模型中是否有 其他列表
Sheet annotation = field.getAnnotation(Sheet.class);
if (annotation != null) {
for (T itme : datas) {// datas中的有多少条数据就有多少个sheet
// 解析sheetName
String sheetItemName = (String) sheetAnnotationResolver.resolver(annotation, itme);
HSSFSheet sheet = null;
// 创建Sheet
if (sheetItemName != null && !sheetItemName.equals("")) {
sheetItemName = sheetItemName + "_" + index;
sheet = wb.createSheet(sheetItemName);
} else {
sheet = wb.createSheet();
}
// 初始化返回数据
Method method;
List itmeData = null;
try {
if (field.getType().equals("boolean")) {// 基本变量
method = clazz.getMethod(getBooleanPrefix(field.getName()));
} else {
method = clazz.getMethod("get" + getMethodName(field.getName()));
}
itmeData = (List) method.invoke(data);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
res.put(sheet, itmeData);
}
index++;
}
}
return res;
}
/**
* 根据sheet来创建表头
*
* @param sheet
* @param data
* @param <T>
* @return
*/
private static <T> void genTitle(HSSFSheet sheet, List<?> data, HSSFWorkbook wb) {
// 创建一行,放表头
if (data == null || data.size() == 0) {
return;
}
// 初始化表头
Class clazz = data.get(0).getClass();
// 在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制
HSSFRow row = sheet.createRow(1);
// 创建单元格,并设置值表头 设置表头居中
HSSFCellStyle style = wb.createCellStyle();
Field[] fields = clazz.getDeclaredFields();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 创建一个居中格式
// 声明列对象
HSSFCell cell = null;
int index = 0;
// 遍历属性创建表头
for (Field field : fields) {
Cell annotation = field.getAnnotation(Cell.class);
if (annotation != null) {// 设置了表头
cell = row.createCell(index);
cell.setCellValue(annotation.value());
cell.setCellStyle(style);
index++;
}
}
// 初始化title
row = sheet.createRow(0);
for (int i = 0; i < index; i++) {
row.createCell(i);
}
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, index - 1));
Title titleAnnotation = (Title) clazz.getAnnotation(Title.class);
String title = titleAnnotation.value();
HSSFCell cell1 = row.getCell(0);
cell1.setCellValue(title);
cell1.setCellStyle(style);
}
/**
* 插入表格数据
*
* @param sheet
* @param data
*/
private static void insertDate(HSSFSheet sheet, List data) {
if (data == null || data.size() == 0) {
return;
}
int index = 1;
for (Object obj : data) {
HSSFRow row = sheet.createRow(index);
Class clazz = data.get(0).getClass();
Field[] fields = clazz.getDeclaredFields();
int fieldIndex = 0;
for (Field field : fields) {
Cell cell = field.getAnnotation(Cell.class);
Object value = null;
if (cell != null) {// 需要填充数据
Method method;
try {
if (field.getType().equals("boolean")) {// 基本变量
method = clazz.getMethod(getBooleanPrefix(field.getName()));
} else {
method = clazz.getMethod("get" + getMethodName(field.getName()));
}
value = method.invoke(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
HSSFCell tabCell = row.createCell(fieldIndex);
if (value != null) {
if (!(value instanceof Boolean) && !(value instanceof Date)) {
String val = value.toString();
tabCell.setCellValue(val);
} else if (value instanceof Boolean) {
tabCell.setCellValue((Boolean) value);
} else if (value instanceof Date) {
tabCell.setCellValue((Date) value);
}
}
fieldIndex++;
}
}
index++;
}
}
public static String getBooleanPrefix(String fieldName) {
String prefix = fieldName.substring(0, 2);
System.out.println(prefix);
if (prefix.equals("is")) {
return fieldName;
} else {
return "is" + getMethodName(fieldName);
}
}
/**
* 获取属性的首字母大写
*
* @param fildeName
* @return
*/
public static final String getMethodName(String fildeName) {
byte[] items = fildeName.getBytes();
items[0] = (byte) ((char) items[0] - 'a' + 'A');
return new String(items);
}
接口:
public interface AnnotationResolver <T extends Annotation> {
/**
* 注解解析
* @param annotation
* @return
*/
Object resolver(T annotation);
Object resolver(T annotation,Object taget);
}
解析@Sheet 注解的注解解析器
public class SheetAnnotationResolver <T extends Annotation> implements AnnotationResolver <T> {
@Override
public Object resolver(T annotation) {
Sheet sheetAnnotation = (Sheet)annotation;
//获取 Sheet字符串
StringBuffer stringBuffer = new StringBuffer(sheetAnnotation.name());
//字符串占位符替换
if (sheetAnnotation.value() != null) {
String[] values = sheetAnnotation.value();
for (int i = 0; i < values.length; i++) {
if (stringBuffer.indexOf("{", 0) != -1) {
stringBuffer.replace(stringBuffer.indexOf("{", 0), stringBuffer.indexOf("}", 0)+1, values[i]);
}
}
}
return stringBuffer.toString();
}
@Override
public Object resolver(T annotation, Object taget) {
Sheet sheetAnnotation = (Sheet)annotation;
//获取 Sheet字符串
StringBuffer stringBuffer = new StringBuffer(sheetAnnotation.name());
//字符串占位符替换
if (sheetAnnotation.value() != null) {
Class clazz = taget.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field :
fields) {
String [] fieldStrs = sheetAnnotation.field();
for (int i = 0; i < fieldStrs.length; i++) {
if (field.getName().equals(fieldStrs[i])) {//是拼接该字段
Method method = null;
Object val =null;
try {
if (field.getType().equals("boolean")) {// 基本变量
method = clazz.getMethod(ExcelGenerator.getBooleanPrefix(field.getName()));
} else {
method = clazz.getMethod("get" + ExcelGenerator.getMethodName(field.getName()));
}
val = method.invoke(taget);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
if (val != null ) {//将注解配置中的字段和
if (stringBuffer.indexOf("{", 0) != -1) {
stringBuffer.replace(stringBuffer.indexOf("{", 0), stringBuffer.indexOf("}", 0)+1, val.toString());
}
}
}
}
}
}
return stringBuffer.toString();
}
public static void main(String[] args) {
String[] values = new String[]{"user","name","age"};
StringBuffer stringBuffer = new StringBuffer("{}qwe;");
for (int i = 0; i < values.length; i++) {
System.out.println(stringBuffer.indexOf("{", 0));
if (stringBuffer.indexOf("{", 0) != -1) {
stringBuffer.replace(stringBuffer.indexOf("{", 0), stringBuffer.indexOf("}", 1)+1, values[i]);
}
}
System.out.println(stringBuffer.toString());
}
}
简单的注解解析,就放在工具类里面直接使用了。
这个代码就算是全部都交代清楚了。
原理就是使用反射 获取传入数据的类型的注解,通过不同的注解,执行不同的时间,最后数据封装在工具类中,省去了每次生成Excel都要构造参数等繁琐的操作,通过注解配置便实现了这一步,实体类中,只有使用@Cell注解的列才能导入到Excel中。
这是一个初级版本,后面可能会加一些功能,比如列类型分组,通过分组可以实现一个实体类可以 实现多种不同的Excel导出列的表格。
样式的控制,通过注解Style 里面通过设置枚举类型来设置样式
字体设置,通过枚举设置字体。
以上说的这些分组、字体、样式这些功能是会在以后的版本中更新。如果小伙伴有什么更好的建议或意见,欢迎来评论。
这里希望能给小伙伴们带来帮助,关注我!不迷路哦!