使用Java导出Excel表格并由浏览器直接下载

您所在的位置:网站首页 excel导出怎么操作 使用Java导出Excel表格并由浏览器直接下载

使用Java导出Excel表格并由浏览器直接下载

2023-08-24 14:57| 来源: 网络整理| 查看: 265

目录

背景描述

技术准备

导出Excel——尝鲜版

导出Excel——封装版(通过反射)

导出Excel——深度封装(设置下拉选项)

 扩展——多个列分别是不同的下拉选项怎么封装

2019-10-28  更新,必看!!!

2019-12-18更新,修复小概率的文件名乱码问题

背景描述

最近博主在做的Web项目中,有一个导出数据到Excel表格的需求,之前用纯JS实现过,这次打算用Java在后端实现,将数据通过response以IO流的方式传输给前端,使浏览器能直接下载。这里做一下记录、笔记。

技术准备

我的项目是基于Spring Boot的,这里只贴出POI框架需要依赖的两个包,其他的都无所谓,只要能提供Controller让浏览器访问即可

org.apache.poi poi 3.14 org.apache.poi poi-ooxml 3.14 导出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表格 * @param sheetName sheet名称 * @param titleList 表头列表 * @param dataList 数据列表 * @return HSSFWorkbook对象 * */ public static HSSFWorkbook createExcel(String sheetName, List titleList,List dataList) throws IllegalAccessException { //创建HSSFWorkbook对象 HSSFWorkbook wb = new HSSFWorkbook(); //创建sheet对象 HSSFSheet sheet=wb.createSheet(sheetName); //在sheet里创建第一行,这里即是表头 HSSFRow rowTitle=sheet.createRow(0); //写入表头的每一个列 for (int i = 0; i < titleList.size(); i++) { //创建单元格 rowTitle.createCell(i).setCellValue(titleList.get(i)); } //写入每一行的记录 for (int i = 0; i < dataList.size(); i++) { //创建新的一行,递增 HSSFRow rowData = sheet.createRow(i+1); //通过反射,获取POJO对象 Class cl = dataList.get(i).getClass(); //获取类的所有字段 Field[] fields = cl.getDeclaredFields(); for (int j = 0; j < fields.length; j++) { //设置字段可见,否则会报错,禁止访问 fields[j].setAccessible(true); //创建单元格 rowData.createCell(j).setCellValue((String) fields[j].get(dataList.get(i))); } } return wb; } }

然后我们模仿一下调用(这里手动制造数据,真实情况下通过数据库查询):

/** * 导出Excel * * * */ @GetMapping(value = "/out-excel-demo") public String outExcelDemo(HttpServletResponse response) throws IOException, IllegalAccessException { //文件名 String fileName = "活动报名情况一览表"; //sheet名 String sheetName = "报名情况sheet"; //表头集合,作为表头参数 List titleList = new ArrayList(); titleList.add("用户账户"); titleList.add("用户名"); titleList.add("报名时间"); titleList.add("是否完成"); //数据对象,这里模拟手动添加,真实的环境往往是从数据库中得到 SignOutExcelJSON excelJSON = new SignOutExcelJSON(); excelJSON.setAccount("18210825916"); excelJSON.setUsername("张三"); excelJSON.setSignDate("2019-08-05"); excelJSON.setFinish("是"); SignOutExcelJSON excelJSON2 = new SignOutExcelJSON(); excelJSON2.setAccount("15939305781"); excelJSON2.setUsername("李四"); excelJSON2.setSignDate("2019-08-01"); excelJSON2.setFinish("否"); //将两个对象加入到集合中,作为数据参数 List excelJSONList = new ArrayList(); excelJSONList.add(excelJSON); excelJSONList.add(excelJSON2); //调取封装的方法,传入相应的参数 HSSFWorkbook workbook = ExcelUtil.createExcel(sheetName,titleList, excelJSONList); //输出Excel文件 OutputStream output=response.getOutputStream(); response.reset(); //中文名称要进行编码处理 response .setHeader("Content-disposition", "attachment; filename="+new String(fileName.getBytes("GB2312"),"ISO8859-1")+".xls"); response.setContentType("application/x-xls"); workbook.write(output); output.close(); return null; }

运行结果:

 

 效果是一样的,而且很灵活。各位可以试一下,假如你导出其他模块的数据,你只需要传入不同的表头集合、实体类集合,就能实现你的需求,这也是封装的魅力所在。

导出Excel——深度封装(设置下拉选项)

假如我有一个需求:是否完成这一列只能输入是否,以下拉框的形式出现。

POI框架肯定有对应的API,大家看文档也能学会,这里我带大家封装一下,毕竟以可重用性为荣。

先封装一个下拉条件对象:

package com.dosion.smart.future.api.entity.activity; import lombok.Data; /** * 导出Excel时的条件,有下列选项时使用 * @author 秋枫艳梦 * @date 2019-08-05 * */ @Data public class OutExcelQuery { //起始行 private int rowStart; //结束行 private int rowEnd; //起始列 private int colStart; //结束列 private int colEnd; //下拉参数 private String[] params; //构造函数 public OutExcelQuery(int rowStart,int rowEnd,int colStart,int colEnd,String[] params){ this.rowStart = rowStart; this.rowEnd = rowEnd; this.colStart = colStart; this.colEnd = colEnd; this.params = params; } }

再贴出来工具类:

package com.dosion.smart.future.utils; import com.dosion.smart.future.api.entity.activity.OutExcelQuery; import com.dosion.smart.future.api.entity.activity.json.SignOutExcelJSON; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; 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表格 * @param sheetName sheet名称 * @param titleList 表头列表 * @param dataList 数据列表 * @param outExcelQuery 下拉选项设置 * @return HSSFWorkbook对象 * */ public static HSSFWorkbook createExcel(String sheetName, List titleList, List dataList, OutExcelQuery outExcelQuery) throws IllegalAccessException { //创建HSSFWorkbook对象(excel的文档对象) HSSFWorkbook wb = new HSSFWorkbook(); //创建sheet对象(excel的表单) HSSFSheet sheet=wb.createSheet(sheetName); //在sheet里创建第一行,这里即是表头 HSSFRow rowTitle=sheet.createRow(0); //写入表头的每一个列 for (int i = 0; i < titleList.size(); i++) { //创建单元格 rowTitle.createCell(i).setCellValue(titleList.get(i)); } //写入每一行的记录 int count = 0; for (int i = 0; i < dataList.size(); i++) { count++; //创建新的一行,递增 HSSFRow rowData = sheet.createRow(i+1); //通过反射,获取POJO对象 Class cl = dataList.get(i).getClass(); //获取类的所有字段 Field[] fields = cl.getDeclaredFields(); for (int j = 0; j < fields.length; j++) { //设置字段可见,否则会报错,禁止访问 fields[j].setAccessible(true); //创建单元格 rowData.createCell(j).setCellValue((String) fields[j].get(dataList.get(i))); } } //如果开启了下拉选项 if (outExcelQuery!=null){ //如果表格中的记录数不是0 if (count!=0){ // 获取下拉列表数据 String[] strs = outExcelQuery.getParams(); //设置哪些行的哪些列为下拉选项 CellRangeAddressList rangeList = new CellRangeAddressList(outExcelQuery.getRowStart(), //结束行为-1时,说明设置所有行 outExcelQuery.getRowEnd()==-1?count:outExcelQuery.getRowEnd(), outExcelQuery.getColStart(),outExcelQuery.getColEnd()); //绑定下拉数据 DVConstraint constraint = DVConstraint.createExplicitListConstraint(strs); //绑定两者的关系 HSSFDataValidation dataValidation = new HSSFDataValidation(rangeList,constraint); //添加到sheet中 sheet.addValidationData(dataValidation); } } return wb; } }

 如果我不想设置任何列为下拉选项,那我调用的时候将最后一个参数传入null即可。如果想设置某一列或某几列为下拉选项,那我调用的时候只需要这样(省略其他代码):

String[] params = new String[]{"是","否"}; //从第一行开始,到最后一行结束,设置第4列为下拉选项 OutExcelQuery outExcelQuery = new OutExcelQuery(1,-1,3,3,params); HSSFWorkbook workbook = ExcelUtil.createExcel(sheetName,titleList,activityService.outExcel(id),outExcelQuery);

 效果:

 扩展——多个列分别是不同的下拉选项怎么封装

以上下拉选项的封装,只是针对某一列或某几列使用相同的下拉选项的情况,假如几个数据列的下拉选项不同呢?

比如,再加一个性别列,下拉选项的值是男和女,此时一张Excel表中就出现了两个下拉选项设置,该怎么封装?

博主就不再写了,留给大家思考,有疑问的可以留言。

提示一个思路:可以应用Java可选参数的特性,传入多个OutExcelQuery对象,进行循环添加条件。

挖坑填坑,其乐融融。

 

2019-10-28  更新,必看!!!

最近有一位博友用到了我的这个工具类,首先表示很荣幸。

但是帮他解决问题的过程中,也发现了这个工具类的一点瑕疵,那就是:

之前的版本,必须要求表头、实体类的字段一一对应,且顺序要一致,比如你导出的表格中有姓名和年龄两个列,那么你的实体类中只能有name和age两个字段,且顺序要一致,否则会出现年龄的值出现在姓名列的情况。

这样确实有点不灵活,如果一个实体类有多个字段呢?如果依然采用这种方式,那我岂不是还要为导出表格专门写一个实体类?

所以,我做了以下改进,在循环写入每一行的列的时候,遍历的是titleList集合的长度,而不是实体类的字段数量,这样一来,我们有一个表头列,就会遍历出对象的几个属性,对象其他的属性将不会体现到表格里。举个例子:

假设导出的表头有姓名、年龄两个列,但是实体类有name、age、sex三个字段,那么我们遍历的是表头的长度,即2,那么sex这个字段将不会被写入表格里。这样一来,实体类就可以有任意个字段了,只需要保证前n个字段与表头保持一致即可。

需要注意的是,你需要导出的字段,在实体类里仍然需要按照表头的顺序进行排列,没办法,只能这么取舍了,否则就做不到万能了,当然大家也可以根据自己的业务去做定制化。

另外,此次增加了Excel的导入功能。

最后贴出来完整的工具类:

import com.dosion.model.activity.query.OutExcelQuery; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.web.multipart.MultipartFile; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 数据导出、导入excel,工具类 * @author 秋枫艳梦 * @date 2019-10-28 * */ public class ExcelUtil { /** * 生成Excel表格 * @param sheetName sheet名称 * @param titleList 表头列表 * @param dataList 数据列表 * @param outExcelQuery 下拉选项设置 * @return HSSFWorkbook对象 * */ public static HSSFWorkbook createExcel(String sheetName, List titleList, List dataList, OutExcelQuery outExcelQuery) throws IllegalAccessException { //创建HSSFWorkbook对象(excel的文档对象) HSSFWorkbook wb = new HSSFWorkbook(); //创建sheet对象(excel的表单) HSSFSheet sheet=wb.createSheet(sheetName); //在sheet里创建第一行,这里即是表头 HSSFRow rowTitle=sheet.createRow(0); //写入表头的每一个列 for (int i = 0; i < titleList.size(); i++) { //创建单元格 rowTitle.createCell(i).setCellValue(titleList.get(i)); } int count = 0; //写入每一行的记录 if (dataList!=null){ for (int i = 0; i < dataList.size(); i++) { count++; //创建新的一行,递增 HSSFRow rowData = sheet.createRow(i+1); //通过反射,获取POJO对象 Class cl = dataList.get(i).getClass(); //获取类的所有字段 Field[] fields = cl.getDeclaredFields(); for (int j = 0; j < titleList.size(); j++) { //设置字段可见,否则会报错,禁止访问 fields[j].setAccessible(true); //创建单元格 rowData.createCell(j).setCellValue((String) fields[j].get(dataList.get(i))); } } } //如果开启了下拉选项 if (outExcelQuery!=null){ //如果表格中的记录数不是0 if (count!=0){ // 获取下拉列表数据 String[] strs = outExcelQuery.getParams(); //设置哪些行的哪些列为下拉选项 CellRangeAddressList rangeList = new CellRangeAddressList(outExcelQuery.getRowStart(), //结束行为-1时,说明设置所有行 outExcelQuery.getRowEnd()==-1?count:outExcelQuery.getRowEnd(), outExcelQuery.getColStart(),outExcelQuery.getColEnd()); //绑定下拉数据 DVConstraint constraint = DVConstraint.createExplicitListConstraint(strs); //绑定两者的关系 HSSFDataValidation dataValidation = new HSSFDataValidation(rangeList,constraint); //添加到sheet中 sheet.addValidationData(dataValidation); } } return wb; } /** * 读入excel文件,解析后返回 * @param file * @throws IOException */ public static List readExcel(MultipartFile file) throws IOException{ //检查文件 checkFile(file); //获得Workbook工作薄对象 Workbook workbook = getWorkBook(file); //创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回 List list = new ArrayList(); if(workbook != null){ for(int sheetNum = 0;sheetNum < workbook.getNumberOfSheets();sheetNum++){ //获得当前sheet工作表 Sheet sheet = workbook.getSheetAt(sheetNum); if(sheet == null){ continue; } //获得当前sheet的开始行 int firstRowNum = sheet.getFirstRowNum(); //获得当前sheet的结束行 int lastRowNum = sheet.getLastRowNum(); //循环除了第一行的所有行 for(int rowNum = firstRowNum+1;rowNum


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3