前言

​ 好长一段时间没有更新博客了,最近刚刚找到实习工作,接触了企业中的项目,在这段时间的实习过程中,终于知道了企业级项目的体量和业务难度跟之前的小项目是完全不同的。10多天的适应期也逐渐让我找到了状态,初步完成了学生到职场新人的转变。

​ 我入职的公司在面试时还提出了一个让我掉进坑里的问题,之前写过一篇文章专门介绍了该问题,有兴趣的同学可以移步查看:面试官问我存储金额应该用哪种数据类型,我竟这样回答

​ 言归正传,最近几天,老大找了几个小功能来让我练手,做了几个小功能模块,基本都是与excel打交道。大概需求如下:导出excel模板,让用户在excel中填写数据做批量添加功能。

​ 因为部分数据是需要从下拉框选取固定的数据,所以需要在导出excel时在对应的单元格列生成下拉框,同时为了让用户明确每个单元格中需要填写的数据格式,在每列的头部也引入了批注。

技术选型

​ 首先让我们来看一下easyExcel中文文档中对这门技术的描述:快速、简单避免OOM的java处理Excel工具

​ 常见的poi和jxl这两种java解析、生成excel的框架都存在一个巨大的缺陷,就是非常的耗内存!而且如果使用poi或者jxl的话需要写大量的底层代码,操作比较繁琐;easypoi虽然对poi进行了解析和封装,简便了读和写的操作。但是由于easypoi对poi的解析方式是dom解析,把结果一次都读入内存操作,这样的操作平时是不会有问题的,但是并发量上来的时候就会出现OOM(OutOfMemory)即内存用完了的问题。而easyExcel对poi进行了封装,运用的SAX的解析方式,明显降低了内存,在简便了操作的同时,也使得java对excel的读或写操作的内存消耗大大降低!

​ 所以综合对比之后选择了阿里开源的easyExcel框架来完成需求。

操作步骤

​ 这里通过一个用户的demo来模拟实际场景。

导入maven依赖

​ 首先当然是要导入maven依赖

<!--easyExcel工具-->
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.3</version>
</dependency>

这里需要注意的是因为easyExcel已经导入了poi的依赖,所以不要再导入poi的依赖了,如果要导入的话要注意一下版本冲突的问题。

web中的写

​ 第一次上手的同学,建议可以直接参照easyExcel的官方中文文档,文档里对操作的描述和代码都比较详细,也有入门的demo,可以根据官网给出的代码先照猫画虎。easyExcel的文档地址我也会在结尾处贴出来。

​ 先看一下web中最基本的写出excel代码:

 @GetMapping("/download")public void download(HttpServletResponse response, String fileName) throws IOException {//设置返回值类型为response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//编码 防止中文乱码fileName = URLEncoder.encode(fileName,"UTF-8");response.setHeader("Content-disposition","attachment;filename="+fileName+".xlsx");List<User> userList = userService.getUser();//easyExcel.write第一个参数是输出流 第二个是依照哪个模板写出//sheet是指设置工作簿名//doWrite就是写出哪些数据EasyExcel.write(response.getOutputStream(), User.class).sheet("用户测试").doWrite(userList);}

​ 这段代码就是把从数据库中查询出来的用户列表依照用户的模板写出到excel,那个用户模板又是啥呢?

​ 我们来看一下代码:

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")
public class User {@ExcelProperty(value = "id",index = 0)@TableId(type = IdType.AUTO)private Long id;@ExcelProperty(value = "用户名",index = 1)private String name;@ExcelProperty(value = "年龄",index = 2)private Integer age;
}

​ 其实就是一个正常的pojo,但是pojo的每个字段上都加上了一个注解@ExcelProperty(),那么这个注解是什么意思呢?其实就是把JavaBean的属性当做是excel表中的一个列,而一条数据就是一行。而value中设置的其实就是该列的表头名称,index就是设置该列在表中的位置,从0开始往后排列。

​ 读到这里可能有同学会发现这不就是数据库吗?是的!java对对象的写出excel其实就可以理解为数据库中的一条条记录,不过是将数据实体化成了一个excel表。

​ 前端可以直接使用a标签来访问下载方法来下载该excel表格。

​ 下载后excel如下:

​ 根据图片可以看出在javaBean中设置了@ExcelProperty()注解的字段,别当做了表头,数据就是通过该注解设置的value属性值,而顺序也是通过index属性来确定的。而下面工作簿的名称则是在写出时通过.sheet()来设置的。

​ 以上就是web中最简单的写,那个复杂一点的业务呢?比如年龄这一列的数据我希望从数据库查出一个列表以供用户选择,这个要怎么实现呢?

下拉框实现

​ 我们可以自定义一个拦截器来完成该需求:

​ 自定义一个拦截器实现SheetWriteHandler接口,重新接口的方法,在afterSheetCreate写逻辑代码即可。

public class SpinnerWriteHandler implements SheetWriteHandler {@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {//定义一个map key是需要添加下拉框的列的index value是下拉框数据Map<Integer,String[]> mapDropDown = new HashMap<>(1);//定义下拉框中的数据 并添加到map中 这里的key是写死的String[] ageArray = {18,19,20,21};mapDropDown.put(2,ageArray);//获取工作簿Sheet sheet = writeSheetHolder.getSheet();///开始设置下拉框DataValidationHelper helper = sheet.getDataValidationHelper();//设置下拉框for (Map.Entry<Integer, String[]> entry : mapDropDown.entrySet()) {/*起始行、终止行、起始列、终止列  起始行为1即表示表头不设置**/CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, entry.getKey(), entry.getKey());/*设置下拉框数据**/DataValidationConstraint constraint = helper.createExplicitListConstraint(entry.getValue());DataValidation dataValidation = helper.createValidation(constraint, addressList);sheet.addValidationData(dataValidation);}}
}

​ 该拦截器会在工作簿创建完成后被执行,思想类似于springAop,在代码动态的插入到源代码的执行中。现在就已经实现了表中某一列的下拉框的设置。因为使用了map来存储数据,所以可以给多个列设置,只需要添加数据到map中即可,key为需要添加列的index,value为一个String[]即下拉框的数据。

​ 在controller层的写出excel代码加一行即可引用该handler使功能实现:

@GetMapping("/download")public void download(HttpServletResponse response, String fileName) throws IOException {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//编码 防止中文乱码fileName = URLEncoder.encode(fileName,"UTF-8");response.setHeader("Content-disposition","attachment;filename="+fileName+".xlsx");List<User> userList = userService.getUser();EasyExcel.write(response.getOutputStream(), User.class).sheet("用户测试")//registerWriteHandler 即为注册拦截器.registerWriteHandler(new SpinnerWriteHandler()).doWrite(userList);}

​ 至此就可以实现excel中的下拉框:


​ 但是这里出现了一个问题,那就是下拉框的数据是写死的,我们希望的应该是从数据库中查出数据,那么应该怎么做呢?

​ 有同学应该就想到了,把这个类加上@Component注解交给Spring管理,再把service实现类注入到该类中,在执行业务时先从数据库查出数据,在对数据做封装即可,代码如下:

@Component
public class SpinnerWriteHandler implements SheetWriteHandler {@Resourceprivate AgeService ageService;@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {List<Age> ageList = ageService.getAge();String[] ageArray = new String[ageList.size()];for (int i = 0; i < ageList.size(); i++) {ageArray[i] = ageList.get(i).getAge().toString();}Map<Integer,String[]> mapDropDown = new HashMap<>();mapDropDown.put(2,ageArray);Sheet sheet = writeSheetHolder.getSheet();///开始设置下拉框DataValidationHelper helper = sheet.getDataValidationHelper();//设置下拉框for (Map.Entry<Integer, String[]> entry : mapDropDown.entrySet()) {/*起始行、终止行、起始列、终止列**/CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, entry.getKey(), entry.getKey());/*设置下拉框数据**/DataValidationConstraint constraint = helper.createExplicitListConstraint(entry.getValue());DataValidation dataValidation = helper.createValidation(constraint, addressList);/*处理Excel兼容性问题**/if (dataValidation instanceof XSSFDataValidation) {dataValidation.setSuppressDropDownArrow(true);dataValidation.setShowErrorBox(true);} else {dataValidation.setSuppressDropDownArrow(false);}sheet.addValidationData(dataValidation);}}
}

​ 但是这条路是行不通的!在代码执行时就会抛出空指针异常,表示ageService是空的!这就证明ageService并未如我们所愿正确注入数据,该问题的具体原因我也没有找到,希望有大佬找到问题原因之后可以在评论区不吝赐教。

​ 既然spring的方法行不通,那么我们可以尝试一下java的办法。以下实现方式是我的实现思路,但是这种实现方式应该存在很大问题,是实打实的笨办法。如果有大佬有更好的实现思路可以在评论区讨论交流。

​ 既然不能通过依赖注入在该代码中做查询,那么可不可以先查出数据再把数据传进来呢?我们可以给这个拦截器设置一个成员变量,提供set方法或带参构造,在controller代码执行时先从数据库查出数据,再把数据传递到该拦截器中,在拦截器中只需要封装数据即可。

​ 该思路的代码实现如下:

​ 拦截器:

public class SpinnerWriteHandler implements SheetWriteHandler {private List<Age> ageList;public SpinnerWriteHandler(List<Age> ageList) {this.ageList = ageList;}public SpinnerWriteHandler() {}@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {String[] ageArray = new String[ageList.size()];for (int i = 0; i < ageList.size(); i++) {ageArray[i] = ageList.get(i).getAge().toString();}Map<Integer,String[]> mapDropDown = new HashMap<>();mapDropDown.put(2,ageArray);Sheet sheet = writeSheetHolder.getSheet();///开始设置下拉框DataValidationHelper helper = sheet.getDataValidationHelper();//设置下拉框for (Map.Entry<Integer, String[]> entry : mapDropDown.entrySet()) {/*起始行、终止行、起始列、终止列**/CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, entry.getKey(), entry.getKey());/*设置下拉框数据**/DataValidationConstraint constraint = helper.createExplicitListConstraint(entry.getValue());DataValidation dataValidation = helper.createValidation(constraint, addressList);sheet.addValidationData(dataValidation);}}
}

​ controller:

 @GetMapping("/download")public void download(HttpServletResponse response, String fileName) throws IOException {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//编码 防止中文乱码fileName = URLEncoder.encode(fileName,"UTF-8");response.setHeader("Content-disposition","attachment;filename="+fileName+".xlsx");List<User> userList = userService.getUser();SpinnerWriteHandler spinnerWriteHandler = new SpinnerWriteHandler(ageService.getAge());EasyExcel.write(response.getOutputStream(), User.class).sheet("用户测试").registerWriteHandler(spinnerWriteHandler).doWrite(userList);}

​ 至此可以实现下拉框的数据从数据库查询需求,如果要实现多个列的下拉框可以通过map传递数据,或者封装一个BO对象,再通过set方法或带参构造传递数据接口。

批注实现

​ 批注的实现跟拦截器思路相近,通过一个拦截器即可实现。

​ 自定义拦截器继承AbstractRowWriteHandler类,重写afterRowDispose方法,在方法中写逻辑代码即可

public class CommentWriteHandler extends AbstractRowWriteHandler {@Overridepublic void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {if (isHead) {Sheet sheet = writeSheetHolder.getSheet();Drawing<?> drawingPatriarch = sheet.createDrawingPatriarch();// 在第一行 第二列创建一个批注Comment comment =drawingPatriarch.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short)1, 0, (short)2, 1));// 输入批注信息comment.setString(new XSSFRichTextString("哈哈哈哈"));// 将批注添加到单元格对象中 从0开始计算 第1行第2列sheet.getRow(0).getCell(1).setCellComment(comment);}}
}

​ 这个拦截器是在行处置之后做业务处理的

​ 需要注意的是:XSSFClientAnchor对象的几个参数有必要说明一下:

XSSFClientAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2)
dx1:起始单元格的x偏移量,填0即可;
dy1:起始单元格的y偏移量,填0即可;
dx2:终止单元格的x偏移量,填0即可;
dy2:终止单元格的y偏移量,填0即可;
col1:起始单元格列序号,从0开始计算;
row1:起始单元格行序号,从0开始计算;
col2:终止单元格列序号,从0开始计算,如例子中col1=1,col2=2就表示单元格列为第二列;
row2:终止单元格行序号,从0开始计算,如例子中row1=0,row2=1就表示单元行为第一行;

对于我们来说,前面4个参数无需关注,只需要关注后面四个参数确定单元格坐标即可,这个位置填写的坐标要与最后一行代码中填写的坐标相对应。

最后在controller中用跟下拉框拦截器同样的方式注册批注拦截器即可:

@GetMapping("/download")public void download(HttpServletResponse response, String fileName) throws IOException {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//编码 防止中文乱码fileName = URLEncoder.encode(fileName,"UTF-8");response.setHeader("Content-disposition","attachment;filename="+fileName+".xlsx");List<User> userList = userService.getUser();SpinnerWriteHandler spinnerWriteHandler = new SpinnerWriteHandler(ageService.getAge());EasyExcel.write(response.getOutputStream(), User.class).sheet("用户测试").registerWriteHandler(spinnerWriteHandler)//注册批注拦截器.registerWriteHandler(new CommentWriteHandler()).doWrite(userList);}

现在关于web中写出带下拉框和批注的excel表格就已经实现了。

总结

​ 使用easyExcel可以方便快捷的完成java对excel表格的操作,其他更多的操作和代码请参见easyExcel的官方文档:easyExcel官方中文文档

​ 如果针对web中easyExcel操作有更好的建议的话,请在评论区讨论交流,不吝赐教。

​ 本文已在掘金同步上传:EasyExcel在项目中的应用-在web中导出带下拉框和批注的excel文件

补充

经过掘金社区评论区大佬的指点,发现了ageService未能成功注入的原因:SpinnerWriteHandler类虽然加了@Component注解交给了spring容器管理,但是在controller层使用的时候,直接去new SpinnerWriteHandler() 调用无参构造去创建了对象,这个对象不是spring的bean 而是一个普通的类 不走spring bean的生命周期 并不会自动注入ageservice

在这里,我要感谢这位大佬的指点,还是我对于springBean的生命周期没有理解透彻,基础不够扎实!要去恶补一下这一块的知识了。

EasyExcel在项目中的应用-在web中导出带下拉框和批注的excel文件相关推荐

  1. 使用EasyExcel导出带下拉框的Excel

    1. 创建注解 import java.lang.annotation.*;/*** 标注导出的列为下拉框类型,并为下拉框设置内容*/ @Documented @Retention(Retention ...

  2. EasyExcel导出带下拉框 通用模板

    实体类 @Data @EqualsAndHashCode(callSuper = true) @ApiModel(value="FireChemicalDto", descript ...

  3. Easyexcel导出带下拉框选项excel模板

    需求:导出的excel模板,表头字段动态生成.sheet页下拉框内容动态生成 解决思路:为了避免excel下拉框选项过多会导致内容不显示,将下拉框的内容都存储在另一个新建的固定的sheet页,再通过引 ...

  4. 导出带下拉选项的Excel基于EasyExcel实现

    前言 今天就是记录一个计算点而已,帮小组成员搽屁股改bug.场景就是导出Excel的模板,希望枚举字段有下拉选择. 一.技术选型场景 我们这里用的是阿里开源的EasyExcel,导出模板是后端动态生成 ...

  5. EasyExcel导出有下拉框的模板

    Conteoller层: 代码: /*** 创建元数据模板* 导出模板** @param response 响应* @throws Exception 异常*/@GetMapping("/c ...

  6. excel添加列下拉框票价_在Excel中制作下拉列表的三种方法

    下拉列表在Excel中的用途十分广泛.在Excel中制作下拉列表可以通过数据有效性.使用窗体控件和VBA控件工具箱中的组合框来制作.下面用一个具体的例子来进行说明(在文章结尾处可下载xls格式的示例文 ...

  7. jQ中的mouseleave和mouseout的区别 模仿下拉框效果

    1.不论鼠标指针离开被选元素还是任何子元素,都会触发 mouseout 事件. 2.只有在鼠标指针离开被选元素时,才会触发 mouseleave 事件. <div class="sel ...

  8. listView中带下拉框和输入框如何获取值

    一:布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:and ...

  9. html实现选择头像,在网页中实现OICQ里的头像选择的下拉框 (附例子)

    模拟oicq下拉头像 var bv=parseint(navigator.appversion); var ie4=((document.all)&&(bv>=4))?true: ...

最新文章

  1. LeetCode简单题之有序数组中出现次数超过25%的元素
  2. 智能车竞赛技术报告 | 单车拉力组 - 沈阳航空航天大学 - 青梅绿茶队
  3. java web开发基础学习
  4. 我的世界java版和基岩版对比_基岩版Beta1.11.0.1发布
  5. ruby on rails ajax select
  6. 列存Delta表是个什么东东
  7. Linux中的僵尸进程处理
  8. Nginx如何实现读写限流的方法
  9. 关于Cocos2d-x中实例伸缩后的位置设置
  10. python 嵌套list的一些小结
  11. 电气simulink常用模块_重庆台达PLC控制柜施工技术标准 - 重庆电工电气
  12. 华为鸿蒙名称由来英文简介,华为鸿蒙出世!中国科研人的那些神仙翻译
  13. 微信公众平台测试账号的注册与申请
  14. 怎么上传云班课的计算机作业,太棒了!云班课助你轻松玩转这9种作业!
  15. java左手画圆右手画方_左手画圆、右手画方,双手齐用同时养护、开发你的左右大脑!...
  16. 股权控制权的五种设计策略
  17. msra数据集_微软机器阅读理解超越人类水平,CoQA数据集得分刷新记录
  18. python循环语句和跳出语句详解
  19. The 2019 ICPC Asia Nanjing Regional Contest(A、C、K)
  20. 优步和滴滴激战中国市场

热门文章

  1. buildroot 树莓派3B+ WiFi
  2. 树莓派3B+蓝牙使用
  3. shell中时间比较
  4. 以太坊区块链 Solidity solc是什么
  5. 织梦dedecms 如何去除版权中的power by dedecms
  6. 品牌对比|罗曼 VS 欧乐B
  7. 自动锁luogu程序源代码
  8. 生物识别产业指纹识别锁在门禁锁的战略性前景
  9. 师者,传道授业解惑者也:看现在的培训机构和毕业生就业状况
  10. android fmod,Android利用Fmod仿QQ变声音效