目录
背景描述
最近博主在做的Web项目中,有一个导出数据到Excel表格的需求,之前用纯JS实现过,这次打算用Java在后端实现,将数据通过response以IO流的方式传输给前端,使浏览器能直接下载。这里做一下记录、笔记。
技术准备
我的项目是基于Spring Boot的,这里只贴出POI框架需要依赖的两个包,其他的都无所谓,只要能提供Controller让浏览器访问即可
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
导出Excel——尝鲜版
这里为了让大家理解POI这个框架一系列的API,先以最low的方式去实现,稍后我们再进行封装,达到“编写一次、处处可用”。
我们只需要提供一个Controller接口:
/**
* 导出数据到Excel
* @param response 响应体
* 注意,我这里是基于Spring Boot的,全局有一个@RestController注解,所以没加@ResponseBody,
* 如果你的不是,请加上@ResponseBody注解
* */
@GetMapping(value = "/out-excel-demo")
public Object outExcelDemo(HttpServletResponse response) throws IOException {
//创建HSSFWorkbook对象(excel的文档对象)
HSSFWorkbook wb = new HSSFWorkbook();
//创建sheet对象(excel的表单)
HSSFSheet sheet=wb.createSheet("sheet1");
//创建第一行,这里即是表头。行的最小值是0,代表每一行,上限没研究过,可参考官方的文档
HSSFRow row1=sheet.createRow(0);
//在这一行创建单元格,并且将这个单元格的内容设为“账号”,下面同理。
//列的最小值标识也是0
row1.createCell(0).setCellValue("账号");
row1.createCell(1).setCellValue("用户名");
row1.createCell(2).setCellValue("日期");
row1.createCell(3).setCellValue("是否完成");
//第二行
HSSFRow row2=sheet.createRow(1);
row2.createCell(0).setCellValue("123456");
row2.createCell(1).setCellValue("张三");
row2.createCell(2).setCellValue("2019-08-05");
row2.createCell(3).setCellValue("是");
//第三行
HSSFRow row3=sheet.createRow(2);
row3.createCell(0).setCellValue("5681464");
row3.createCell(1).setCellValue("李四");
row3.createCell(2).setCellValue("2019-08-01");
row3.createCell(3).setCellValue("否");
//输出Excel文件
OutputStream output=response.getOutputStream();
response.reset();
response
.setHeader("Content-disposition", "attachment; filename=demo.xls");
response.setContentType("application/x-xls");
wb.write(output);
output.close();
return null;
}
然后你可以在页面上写一个按钮,点击的时候通过location.href指向上面的接口路径,我这里就省略了,看一下效果:
打开表格:
到这里,相信大家对POI有认识了吧?
其实它就是以每个HSSFRow为一个主体,每一个HSSFRow代表一行记录,我们只需要通过这个对象的createCell方法去创建单元格、赋值就行,这样就很清晰了吧?
导出Excel——封装版(通过反射)
以上我们实现了简单的数据导出,但是实际的场景根本不是这样,我们都是从数据库里查出来数据,而且不可能这样一行一行的去设置。
你肯定想到了循环,没错,循环是肯定的,但是仅仅循环还不够灵活,为什么呢?
根据面向对象的思维,我们可以将所有的表头(即第一行)做成一个List集合参数,将所有的数据做成一个List集合参数,这个数据集合的泛型是我们的POJO实体类,然后我们两个循环就能省略一大段代码。
但是问题来了,我们例子中的导出表格,是“账号、用户名、日期、是否完成”这四个表头,实体类也是对应的四个属性。假如又来了一个导出需求呢?表头不一样了,所对应的实体类也不一样了,难道我们再封装成一个其他的方法?难道每个不同的Excel表结构都要封装一个新的方法吗?
做的时候博主立马就想到了反射机制,我们可以传入List集合,对泛型不做限制,遍历数据集合的时候,通过反射得到这个对象的字段,动态赋值。
但是这就有一个强制要求:在实体类声明字段的时候,顺序必须和表头的前后顺序一致,否则循环遍历的时候会出现数据不对应的现象。
首先我们声明一个实体类,这也符合我们真正的开发环境:
package com.dosion.smart.future.api.entity.activity.json;
import lombok.Data;
/**
* 导出报名情况的数据传输对象
* @author 秋枫艳梦
* @date 2019-08-05
* */
@Data
public class SignOutExcelJSON {
//用户账号
private String account;
//用户名
private String username;
//报名时间
private String signDate;
//是否完成
private String finish;
}
然后封装一个工具类出来:
package com.dosion.smart.future.utils;
import com.dosion.smart.future.api.entity.activity.json.SignOutExcelJSON;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddress;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 数据导出excel,工具类
* @author 秋枫艳梦
* @date 209-08-05
* */
public class ExcelUtil {
/**
* 生成Excel表格