导出结果为excel是相对常见的业务需求,大部分情况下只需要导出简单的格式即可,所以有许多可以采用的方案。有些方案还是很容易实现的。

一、可用的解决方案

目前可以有几类解决方案:

  1. 字处理企业提供的解决方案 -- 目前这个还没有看到,大概这些企业不屑于与民争利或者因为某些理由没有直接参与。例如微软,金山等都没有提供。如果有,就会压死一批三方产商。不过估计原厂看不上,或者是故意让利
  2. 三方产商提供的符合规范的解决方案
    1. APACHE poi,这是最著名的解决方案,已经存在20来年了。最早版本Version 0.1 (2001-08-28)。
    2. DOCX4J,也可以用,不过用得相对少。https://www.docx4java.org/ 。有提供收费的,企业级别的解决方案
    3. easyExcel,这是阿里的,本质是对POI的封装。让酷爱注解人士方便了不少。excel编程好像可以简化为对几个注解的背诵。
    4. 其它,暂时没有收集
  3. 各个公司自己的简易解决方案
    1. 输出csv的,这个是取巧的
    2. 输出xml的,这个也是取巧
    3. 其它,暂无收集

poi+注解

如果想简单一点处理,那么可以考虑easyExcel。

如果赶项目,而且客户对于excel格式没有什么要求,也是可以考虑的,毕竟这也节省了时间,某些项目中,这是项目经理所偏爱的,因为可能节约很多时间。

但是这种注解方式局限性也比较大,只能用于POJO/bean,格式无法定制,写一个查询就需要一个POJO。

此外,如果用于导出几万行数据,那么很可能会让某些人受不了,有点偏慢了,这种慢,在目前jvm版本下,没有什么特别好的方法。因为利用这种方式,意味着以下几个多余的步骤要做至少几万次:

a.数据映射为pojo,每一行的每个列都需要反射重复的判断等,隐式转换。

在某些业务场景下,这可能是一个不大不小的问题。此外跑得慢,消耗的能源自然也多一些。

当然最大的问题是定制excel样式比较麻烦。如果一定要坚持用这个实现也可以,不过最后工作就变为一部分人写POJO+注解,一个核心去实现注解。

最后从目前的jvm版本来说,注解+反射总是会慢了一些(不过现在大家也不是太关注这个)。

 poi(无注解)

非注解方式,足够灵活,可以避免写POJO和注解。通过定制一套工具,可以非常容易地实现一个简单的综合查询报表系统,并且可以实现比较简单的导出,

配合上模板,有时候还是不错的。

我个人倾向于直接使用poi,自己项目和团队中编写一些公用的工具。其它的方案暂时对我没有吸引力。

二、poi直接导出结果到http响应流

这里不讨论极限编程的事情,主要考虑导出一些不算太大的excel,例如10万行之类的。如果更大,一般不这么做了。

由于项目的需要,生成的excel无需缓存到服务器本地,而是可以直接输出到http响应流。

以下是例子代码,仅仅实例如何输出一个非常简单的excel导出。

环境:windows11,jdk1.8,springboot 2.6.7,poi-5.2.2,jquery 3.6.0,edge

2.1后端

pom.xml

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.2</version>
</dependency>

核心代码(部分代码出处不可考):

import java.net.URLEncoder;
import java.util.List;
import java.util.Map; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
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.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import tools.model.ExportExcelParam; /**
* excel导出工具
*
* @author lzfto
* @since
*/ public class ExcelTool { /**
* 导出excel到http输出流
*
* @param param
*/
public static void exportExcelToHttpResponse(ExportExcelParam param) {
outExcel(param.getFileName(), param.getColList(), param.getHeaderTitle(), param.getDataList(),
param.getResponse(), param.getUserAgent());
} /**
* 导出文件到 http响应流
* @param fileName 必须xls,xlsx之一为后缀
* @param colList key列表,非空-listMap中,map的key列表,多个以逗号分割。
* @param headerTitle 表头-非空。多个以逗号分割
* @param dataList ListMap-非空。map中的key必须和colList对应
* @param response Http响应
* @param userAgent 客户端信息(暂时不支持移动端)
* @apiNote 如果dataList的行数大于5000条,会采用XSSFWorkbook,避免内存溢出
* 此外,如果实在太大,那么请不要采用这个方法,而应该采用缓存+断点须传
*/
private static void outExcel(String fileName, String colList, String headerTitle,
List<Map<String, Object>> dataList, HttpServletResponse response, String userAgent) { // 第一步,创建一个workbook,对应一个Excel文件
Workbook wb = null;
if(dataList.size()<5000) {
if (fileName.endsWith(".xls")) {
wb = new HSSFWorkbook();
} else if (fileName.endsWith(".xlsx")) {
wb = new XSSFWorkbook();
}
}
else {
wb=new SXSSFWorkbook();
} // 第二步,在workbook中添加一个sheet,对应Excel文件中的sheet
Sheet sheet = wb.createSheet("sheet1");
// 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制short
Row row = sheet.createRow((int) 0);
// 第四步,创建单元格,并设置值表头 设置表头居中
CellStyle style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER); // 创建一个居中格式
style.setFillForegroundColor(HSSFColor.HSSFColorPredefined.GREEN.getIndex());
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
// 为表头生成一个字体
Font font = wb.createFont();
font.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());
font.setFontHeightInPoints((short) 12);
font.setBold(true);
// 把字体应用到当前的样式
style.setFont(font); Cell cell = null;
String[] headerTitleArr = headerTitle.split(",");
for (int i = 0; i < headerTitleArr.length; i++) {
cell = row.createCell((short) i);
cell.setCellValue(headerTitleArr[i]);
cell.setCellStyle(style);
} // 第五步,写入实体数据 实际应用中这些数据从数据库得到
// 如果有需要,可以为内容设置一个字体,此处略
String[] columnList = StringUtils.split(colList, ',');
for (int i = 0; i < dataList.size(); i++) {
row = sheet.createRow((int) i + 1);
Map<String, Object> dataMap = dataList.get(i);
for (int j = 0; j < columnList.length; j++) {
cell = row.createCell((short) j);
String value = "";
if (dataMap.get(columnList[j]) != null) {
value = dataMap.get(columnList[j]).toString();
}
cell.setCellValue(value);
}
} for (int i = 0; i < headerTitleArr.length; i++) {
sheet.setColumnWidth(i, headerTitleArr[i].getBytes().length * 2 * 256);
} // 第六步,将excel内存信息写入到http响应流
try {
String outputFileName = fileName;
if (userAgent.toUpperCase().indexOf("MSIE") > 0) {
outputFileName = URLEncoder.encode(fileName, "UTF-8");
} else if (userAgent.toUpperCase().indexOf("IPHONE") > 0) {
outputFileName = new String(fileName.getBytes(), "ISO-8859-1");
} else {
outputFileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");
}
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment; filename=\"" + outputFileName + "\"");
wb.write(response.getOutputStream());
response.getOutputStream().flush(); } catch (Exception e) {
System.out.println(e.getMessage());
}
} }

这里需要注意的是:内存溢出问题,这是使用SXSSFWorkbook 完成的,但这个东西目前限制比较多。

关于SXSSFWorkbook ,可以参阅:https://poi.apache.org/components/spreadsheet/

这里摘取一些资料:

Since 3.8-beta3, POI provides a low-memory footprint SXSSF API built on top of XSSF.

SXSSF is an API-compatible streaming extension of XSSF to be used when very large spreadsheets have to be produced, and heap space is limited.
SXSSF achieves its low memory footprint by limiting access to the rows that are within a sliding window, while XSSF gives access to all rows in the document.
Older rows that are no longer in the window become inaccessible, as they are written to the disk. In auto-flush mode the size of the access window can be specified, to hold a certain number of rows in memory.
When that value is reached, the creation of an additional row causes the row with the lowest index to to be removed from the access window and written to disk.
Or, the window size can be set to grow dynamically; it can be trimmed periodically by an explicit call to flushRows(int keepRows) as needed. Due to the streaming nature of the implementation, there are the following limitations when compared to XSSF:
.Only a limited number of rows are accessible at a point in time.
.Sheet.clone() is not supported.
.Formula evaluation is not supported
See more details at SXSSF How-To

原文下面有个图,略。

这个东西怎么用,建议好好阅读 https://poi.apache.org/components/spreadsheet/how-to.html#sxssf 和官方api文档。 更多介绍略(咱暂时也不用)。

2.2前端

原生js方案

/**
* 导出所有满足条件的内容为excel格式
*/
function exportClick() {
let _param = getParamValue();
var url = '/log/srv/export';
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true); // 也可以使用POST方式,根据接口
xhr.setRequestHeader('content-type', 'application/json');
xhr.responseType = "blob"; // 返回类型blob
// 定义请求完成的处理函数,请求前也可以增加加载框/禁用下载按钮逻辑
xhr.onload = function () {
// 请求完成
if (this.status === 200) {
// 返回200
var blob = this.response;
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (e) {
// 转换完成,创建一个a标签用于下载
var a = document.createElement('a');
a.download = '服务日志.xlsx';
a.href = e.target.result;
$("body").append(a);
a.click();
$(a).remove();
};
}
};
// 发送ajax请求
xhr.send(JSON.stringify(_param));
}

这是非常简单的代码,没有特别的优化,以及异常处理等等。

jquery方案

也可以用jquery处理,例如:

function downAsExcel() {
  let _param = getParamValue();
  $.ajax({
    url: '/log/srv/export',
    type: 'POST',
    dataType: 'blob',
    contentType: "application/json",
    async: true,
    data: JSON.stringify(_param),
    success: function (rs, status, xhr) {
      var blob = rs;
      var reader = new FileReader();
      reader.readAsDataURL(blob);
      reader.onload = function (e) {
        // 转换完成,创建一个a标签用于下载
        var a = document.createElement('a');
        a.download = '服务日志.xlsx';
        a.href = e.target.result;
        $("body").append(a);
        a.click();
        $(a).remove();
      };
    },
    error: function (rs) {
      showMessgeBox(_MSG_ERR, '网络错误,汇总失败', 1);
    }
  });
}

三、小结

如果厌倦注解,并且想灵活一些,建议直接使用POI来处理EXCEL的导出。

POI的功能还是很强大的,虽然极限情况和复杂情况处理的不够好(毕竟不是原厂),但是大部分情况下,已经足够用了。

如果不是为了导出,那么完全可以使用厂商提供的api来处理文档,例如微软和金山都有提供类似的开放api,实现得很完美。

JAVA-poi导出excel到http响应流的更多相关文章

  1. 重构:以Java POI 导出EXCEL为例

    重构 开头先抛出几个问题吧,这几个问题也是<重构:改善既有代码的设计>这本书第2章的问题. 什么是重构? 为什么要重构? 什么时候要重构? 接下来就从这几个问题出发,通过这几个问题来系统的 ...

  2. Java POI 导出EXCEL经典实现 Java导出Excel

    转自http://blog.csdn.net/evangel_z/article/details/7332535 在web开发中,有一个经典的功能,就是数据的导入导出.特别是数据的导出,在生产管理或者 ...

  3. 重构:以Java POI 导出EXCEL为例2

    前言 上一篇博文已经将一些对象抽象成成员变量以及将一些代码块提炼成函数.这一节将会继续重构原有的代码,将一些函数抽象成类,增加成员变量,将传入的参数合成类等等. 上一篇博文地址:http://www. ...

  4. java:POI导出excel

    POI是一个开源项目,专用于java平台上操作MS OFFICE,企业应用开发中可用它方便导出Excel. 下面是使用示例: 1.maven中先添加依赖项 <dependency> < ...

  5. java POI导出Excel文件数据库的数据

    在web开发中,有一个经典的功能,就是数据的导入导出.特别是数据的导出,在生产管理或者财务系统中用的非常普遍,因为这些系统经常要做一些报表打印的工作.这里我简单实现导出Excel文件. POI jar ...

  6. Java POI 导出excel表

    1.首先下载poi-3.6-20091214.jar,下载地址如下: http://download.csdn.net/detail/evangel_z/3895051 2.Student.java ...

  7. Java POI导出Excel不弹框选择下载路径(下载文件不选择下载路径,默认) Chrome

    在Chrome浏览器中,Java导出Excel文件时,浏览器弹出提示框,需要选择下载路径 在Chrome中的高级设置中,把“下载前询问每个文件的保存位置”去掉就解决了 DEEPLOVE(LC)

  8. JAVA POI导出EXCEL 动态表头、多级表头、动态数据

    导出Excel文件是业务中经常遇到的需求,以下是经常遇到的一些问题: 1,导出中文文件名乱码 String filename = "sheet1";response.setChar ...

  9. java poi导出EXCEL xls文件代码

    String _currentPage = request.getParameter("currentPage"); Integer currentPage = 0; if(_cu ...

  10. Java POI 导出EXCEL经典实现

    import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; i ...

随机推荐

  1. Roslyn 分析器 EnforceExtendedAnalyzerRules 属性的作用

    在开始编写 dotnet 的 Roslyn 分析器项目时,会被 VisualStudio 通过 RS1036 要求在项目文件配置上 EnforceExtendedAnalyzerRules 属性,本文 ...

  2. 微服务从代码到k8s部署应有尽有系列全集

    微服务从代码到k8s部署应有尽有系列全集 https://mp.weixin.qq.com/s/8U2h85YJHy3H3hzlBakPPw 实战项目地址:https://github.com/Mik ...

  3. 如何在docker环境下的纯净ubuntu系统中安装最新版nginx

    原文件地址: https://www.cnblogs.com/taoshihan/p/11588269.html 视频地址: https://www.bilibili.com/video/av6898 ...

  4. ansible(7)--ansible的file模块

    1. file模块 功能:为被控端创建文件或目录,设定权限属性: 主要参数如下: 参数 说明 path 指定远程服务器的路径,也可以写成'dest','name' state 状态,可以将值设定为di ...

  5. Java 获取两个时间的时间差

    前言 在平时的工作中,难免会遇到获取两个时间相差多少天.小时.分钟.秒.毫秒,现在我将自己获取的方法总结如下: 一.导入需要的依赖 <dependency> <groupId> ...

  6. SQL如何删除所有字段都相同的重复数据?

    SQL Server数据库:有时候在处理数据时会遇到不加主键的表,导致数据表内出现了一模一样的数据,刚开始第一时间想到的方式是,把两条数据全部删除,然后再插入一条,但是这种可能数据量比较少的话,还可以 ...

  7. python教程6.4-json序列化

    序列化:dumps,编码,将python类型转成json对象 反序列化:loads,解码,将json对象转成python对象 pickle 模块提供了四个功能:dumps.loads.dump.loa ...

  8. C#TMS系统学习(ShippingNotice页面)

    C#TMS系统代码-业务页面ShippingNotice学习 学一个业务页面,ok,领导开完会就被裁掉了,很突然啊,他收拾东西的时候我还以为他要旅游提前请假了,还在寻思为什么回家连自己买的几箱饮料都要 ...

  9. NumPy 分割与搜索数组详解

    NumPy 分割数组 NumPy 提供了 np.array_split() 函数来分割数组,将一个数组拆分成多个较小的子数组. 基本用法 语法: np.array_split(array, indic ...

  10. 大数据之Hadoop的HDFS存储优化—异构存储(冷热数据分离)

    异构存储主要解决,不同的数据,储存在不同类型的硬盘中,达到最佳性能的问题 1)存储类型 RAM_DISK:内存镜像文件系统 SSD:SSD固态硬盘 DISK:普通磁盘,在HDFS中,如果没有主动声明数 ...