阿里的easyexcal包实现表格动态导出
阿里的easyexcal包實(shí)現(xiàn)表格動(dòng)態(tài)導(dǎo)出
1.介紹
在日常開發(fā)中,我們或多或少會(huì)遇到導(dǎo)入excal,導(dǎo)出excal等業(yè)務(wù)需求,那么了解這一技能就很有必要了。
市場(chǎng)中針對(duì)這個(gè),我知道的有兩個(gè)包,一個(gè)是poi(Poor Obfuscation Implementation),一個(gè)是easyexcal(阿里的);但poi存在oom內(nèi)存溢出的風(fēng)險(xiǎn),在正式環(huán)境中,導(dǎo)出的數(shù)據(jù)往往成千上萬(wàn)條,很容易就觸發(fā)oom,所以,一般情況下公司都不建議使用poi,阿里的easyexcal包的底層實(shí)現(xiàn)也是使用了poi,也就是說(shuō)easyexcal是對(duì)poi的進(jìn)一步封裝改造,成功規(guī)避了oom。
2.場(chǎng)景需求
現(xiàn)在有一個(gè)需求:實(shí)現(xiàn)表頭信息動(dòng)態(tài),表數(shù)據(jù)動(dòng)態(tài),表中單元格具有各自的樣式(比如背景顏色,字體樣式,字體顏色等)。
3.設(shè)計(jì)方案思路
我這里使用easyexcal包實(shí)現(xiàn)該需求,首先注意表頭信息是變動(dòng)的,那也就意味著沒法使用easyexcal的一系列注解(就是常規(guī)的先創(chuàng)建一個(gè)導(dǎo)出的實(shí)體類,然后再加注解),這里有個(gè)坑:我最開始的想法是,創(chuàng)建包含所有表頭信息的導(dǎo)出實(shí)體,然后通過(guò)反射的技術(shù)對(duì)具體需要展示的表頭字段進(jìn)行標(biāo)識(shí),然后發(fā)現(xiàn)easyexcal中的注解@ExcelIgnore 忽略項(xiàng)是沒有value屬性的,這樣就出現(xiàn)了尷尬的情況:反射能給字段添加或刪除注解嗎???,我查了很多資料,最后是沒找到,應(yīng)該不能實(shí)現(xiàn)添加或刪除,但給注解中的屬性值修改是可以做到的,但@ExcelIgnore沒有屬性供我們修改,至此,該方法行不通了;然后我看了easyexcal源碼,大致知道了導(dǎo)出excal的流程:先渲染表頭,再渲染數(shù)據(jù),他們都是一個(gè)個(gè)單元格,每一個(gè)單元格有自己的行列數(shù),一個(gè)excal表就是由一個(gè)個(gè)單元格以此拼接起來(lái)的。于是,我就使用單獨(dú)給表頭數(shù)據(jù),表體數(shù)據(jù),需要給單元格加樣式的行列坐標(biāo)數(shù)據(jù)和樣式數(shù)據(jù)。需要注意的是表頭和表體數(shù)據(jù)格式是二維數(shù)組,我是用list中套list,外層list中的元素是一行,內(nèi)部list中的元素是列。
4.具體代碼實(shí)現(xiàn)
我這里分別使用了3.0.5和2.2.8的easyexcal包,需要注意的是3.0版本之前和之后有較大改動(dòng),其實(shí)現(xiàn)的方法不一樣,注意:以下案例在springboot項(xiàng)目中實(shí)現(xiàn)
4.1easyexcal3.0.5版本實(shí)現(xiàn)代碼
4.1.1導(dǎo)核心依賴
4.1.2寫一個(gè)類繼承AbstractCellWriteHandler或者也可以實(shí)現(xiàn)CellWriteHandler接口
public class MyCellStyleWriteHandler extends AbstractCellWriteHandler {private List<XyInfo> xyInfo;/*給樣式模板兩個(gè)*/private CellStyle style1;private CellStyle style2;public MyCellStyleWriteHandler(){}public MyCellStyleWriteHandler(List<XyInfo> xyInfo){this.xyInfo=xyInfo;}@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {// 設(shè)置行高測(cè)試}@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {Cell cell = context.getCell();// 拿到poi的workbookWorkbook workbook = context.getWriteWorkbookHolder().getWorkbook();// 自定義寬度處理// 自定義樣式處理// 當(dāng)前事件會(huì)在 數(shù)據(jù)設(shè)置到poi的cell里面才會(huì)回調(diào)// 判斷不是頭的情況 如果是fill 的情況 這里會(huì)==null 所以用not trueif (BooleanUtils.isNotTrue(context.getHead())) {//循環(huán)樣式信息,進(jìn)行橫縱坐標(biāo)的匹配,給對(duì)應(yīng)的單元格樣式for (XyInfo item:xyInfo) {if (cell.getRowIndex() == item.getX() && cell.getColumnIndex() == item.getY()) {//現(xiàn)在已經(jīng)鎖定的單元格,下面只需要給樣式//樣式: 1:紅字,紫色背景;2:黃字,紅色背景// 這里千萬(wàn)記住 想辦法能復(fù)用的地方把他緩存起來(lái) 一個(gè)表格最多創(chuàng)建6W個(gè)樣式,我這就直接樣式模板化,避免其發(fā)生if(item.getContent()==1){cell.setCellStyle(this.getCellStyle(workbook,1));}if(item.getContent()==2){cell.setCellStyle(this.getCellStyle(workbook,2));}// 由于這里沒有指定dataformat 最后展示的數(shù)據(jù) 格式可能會(huì)不太正確// 這里要把 WriteCellData的樣式清空, 不然后面還有一個(gè)攔截器 FillStyleCellWriteHandler 默認(rèn)會(huì)將 WriteCellStyle 設(shè)置到// cell里面去 會(huì)導(dǎo)致自己設(shè)置的不一樣(很關(guān)鍵)context.getFirstCellData().setWriteCellStyle(null);}}}}//優(yōu)化代碼,給個(gè)樣式方法/*** 對(duì)于可確定的樣式,進(jìn)行樣式模板化,避免每一個(gè)單元格都創(chuàng)建一個(gè)樣式* @param workbook 工作簿對(duì)象* @param content 具體設(shè)置樣式信息* @return*/public CellStyle getCellStyle(Workbook workbook,Integer content){//樣式: 1:紅字,紫色背景;2:黃字,紅色背景if(null==this.style1 && content==1){//避免多次創(chuàng)建//這里有個(gè)坑:我最初的想法是自己創(chuàng)建CellStyle 對(duì)象,通過(guò)new的方式,然后這個(gè)地方不認(rèn)他,設(shè)置完全不起效果,所以這里只能從weekbook中拿。style1=workbook.createCellStyle();Font font=workbook.createFont();//紅字font.setColor((short)016);//藍(lán)色背景style1.setFillForegroundColor((short)030);//加載字體style1.setFont(font);// 這里需要指定 FillPatternType 為FillPatternType.SOLID_FOREGROUNDstyle1.setFillPattern(FillPatternType.SOLID_FOREGROUND);}if(null==style2 && content==2){//避免多次創(chuàng)建style2=workbook.createCellStyle();Font font=workbook.createFont();//白字font.setColor((short)011);//紅色背景style2.setFillForegroundColor((short)016);//加載字體style2.setFont(font);// 這里需要指定 FillPatternType 為FillPatternType.SOLID_FOREGROUNDstyle2.setFillPattern(FillPatternType.SOLID_FOREGROUND);}//返回樣式模板if(content==1){return this.style1;}if (content==2){return this.style2;}return null;} }4.1.3導(dǎo)出關(guān)鍵代碼
public class Test9 {public static void main(String[] args) {//你要導(dǎo)出的文件存放路徑和文件名字String filePath = "D:\\ttt\\Download\\";String fileName=System.currentTimeMillis() + ".xlsx";File file = new File(filePath);if (!file.exists()){file.mkdirs();}//解析表頭容器List<List<String>>headss=new LinkedList<>();//解析數(shù)據(jù)容器List<List<String>>datas=new LinkedList<>();//解析單元格樣式容器List<XyInfo>xyInfo=new LinkedList<>();//手動(dòng)添加假數(shù)據(jù)模擬真實(shí)數(shù)據(jù)(表頭信息)headss.add( new ArrayList(new LinkedList<String>(Arrays.asList("A","AA"))));headss.add( new ArrayList(new LinkedList<String>(Arrays.asList("B","AAA"))));headss.add( new ArrayList(new LinkedList<String>(Arrays.asList("C","A2","A3","A4"))));//手動(dòng)添加假數(shù)據(jù)模擬真實(shí)數(shù)據(jù)(標(biāo)體數(shù)據(jù))datas.add(new LinkedList<String>(Arrays.asList("aaa","bbb","")));datas.add(new LinkedList<String>(Arrays.asList("aaa","bbb","ccc")));datas.add(new LinkedList<String>(Arrays.asList("aaa","","")));//手動(dòng)添加需要設(shè)置樣式的單元格信息xyInfo.add(new XyInfo(6,0,1));xyInfo.add(new XyInfo(5,1,2));EasyExcel.write(filePath+fileName)// 這里放入動(dòng)態(tài)頭.head(headss).sheet("模板(sheet名字)")//加載單元格樣式.registerWriteHandler(new MyCellStyleWriteHandler(xyInfo)).doWrite(datas);System.out.println("導(dǎo)出成功");} }XyInfo實(shí)體類
@Data public class XyInfo {/*** 行*/private Integer x=0;/*** 列*/private Integer y=0;/*** 樣式: 1:紅字,紫色背景;2:黃字,紅色背景*/private Integer content;public XyInfo(Integer x,Integer y,Integer content){this.x=x;this.y=y;this.content=content;} }4.1.4效果展示
4.2easyexcal2.2.8版本實(shí)現(xiàn)代碼
4.2.1導(dǎo)核心依賴
4.2.2寫一個(gè)類實(shí)現(xiàn)CellWriteHandler接口
public class My2 implements CellWriteHandler {private List<XyInfo> xyInfo;/*給樣式模板兩個(gè)*/private CellStyle style1;private CellStyle style2;public My2(List<XyInfo> xyInfo){this.xyInfo=xyInfo;}/*** 在創(chuàng)建單元格之前調(diào)用*/@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {}/*** 在單元格創(chuàng)建后調(diào)用*/@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {}/*** 在單元上的所有操作完成后調(diào)用*/@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {// 拿到poi的workbookWorkbook workbook = cell.getSheet().getWorkbook();// 自定義寬度處理// 自定義樣式處理// 當(dāng)前事件會(huì)在 數(shù)據(jù)設(shè)置到poi的cell里面才會(huì)回調(diào)// 判斷不是頭的情況 如果是fill 的情況 這里會(huì)==null 所以用not true//循環(huán)樣式信息,進(jìn)行橫縱坐標(biāo)的匹配,給對(duì)應(yīng)的單元格樣式for (XyInfo item:xyInfo) {if (cell.getRowIndex() == item.getX() && cell.getColumnIndex() == item.getY()) {//現(xiàn)在已經(jīng)鎖定的單元格,下面只需要給樣式//樣式: 1:紅字,紫色背景;2:黃字,紅色背景// 這里千萬(wàn)記住 想辦法能復(fù)用的地方把他緩存起來(lái) 一個(gè)表格最多創(chuàng)建6W個(gè)樣式,我這就直接樣式模板化,避免其發(fā)生if(item.getContent()==1){cell.setCellStyle(this.getCellStyle(workbook,1));}if(item.getContent()==2){cell.setCellStyle(this.getCellStyle(workbook,2));}}}}//優(yōu)化代碼,給個(gè)樣式方法/*** 對(duì)于可確定的樣式,進(jìn)行樣式模板化,避免每一個(gè)單元格都創(chuàng)建一個(gè)樣式* @param workbook 工作簿對(duì)象* @param content 具體設(shè)置樣式信息* @return*/public CellStyle getCellStyle(Workbook workbook,Integer content){//樣式: 1:紅字,紫色背景;2:黃字,紅色背景if(null==this.style1 && content==1){//避免多次創(chuàng)建style1=workbook.createCellStyle();Font font=workbook.createFont();//紅字font.setColor((short)016);//藍(lán)色背景style1.setFillForegroundColor((short)030);//加載字體style1.setFont(font);// 這里需要指定 FillPatternType 為FillPatternType.SOLID_FOREGROUNDstyle1.setFillPattern(FillPatternType.SOLID_FOREGROUND);}if(null==style2 && content==2){//避免多次創(chuàng)建style2=workbook.createCellStyle();Font font=workbook.createFont();//白字font.setColor((short)011);//紅色背景style2.setFillForegroundColor((short)016);//加載字體style2.setFont(font);// 這里需要指定 FillPatternType 為FillPatternType.SOLID_FOREGROUNDstyle2.setFillPattern(FillPatternType.SOLID_FOREGROUND);}//返回樣式模板if(content==1){return this.style1;}if (content==2){return this.style2;}return null;} }4.2.3導(dǎo)出關(guān)鍵代碼
public class Test10 {public static void main(String[] args) {//你要導(dǎo)出的文件存放路徑和文件名字String filePath = "D:\\ttt\\Download\\";String fileName=System.currentTimeMillis() + ".xlsx";File file = new File(filePath);if (!file.exists()){file.mkdirs();}//解析表頭容器List<List<String>>headss=new LinkedList<>();//解析數(shù)據(jù)容器List<List<String>>datas=new LinkedList<>();//解析單元格樣式容器List<XyInfo>xyInfo=new LinkedList<>();//手動(dòng)添加假數(shù)據(jù)模擬真實(shí)數(shù)據(jù)(表頭信息)headss.add( new ArrayList(new LinkedList<String>(Arrays.asList("A","AA"))));headss.add( new ArrayList(new LinkedList<String>(Arrays.asList("B","AAA"))));headss.add( new ArrayList(new LinkedList<String>(Arrays.asList("C","A2","A3","A4"))));//手動(dòng)添加假數(shù)據(jù)模擬真實(shí)數(shù)據(jù)(標(biāo)體數(shù)據(jù))datas.add(new LinkedList<String>(Arrays.asList("aaa","bbb","")));datas.add(new LinkedList<String>(Arrays.asList("aaa","bbb","ccc")));datas.add(new LinkedList<String>(Arrays.asList("aaa","","")));//手動(dòng)添加需要設(shè)置樣式的單元格信息xyInfo.add(new XyInfo(6,0,1));xyInfo.add(new XyInfo(5,1,2));EasyExcel.write(filePath+fileName)// 這里放入動(dòng)態(tài)頭.head(headss).sheet("模板(sheet名字)")//加載單元格樣式.registerWriteHandler(new My2(xyInfo)).doWrite(datas);System.out.println("導(dǎo)出成功");} }4.2.3效果展示
5.總結(jié)
到此,該需求基本實(shí)現(xiàn),現(xiàn)在就簡(jiǎn)單說(shuō)說(shuō)easyexcal包中的技術(shù),使用了攔截器技術(shù),aop思想,動(dòng)態(tài)代理等,具體的,我后續(xù)會(huì)做整理,目前還在研究源碼,最后希望此文章能給你帶來(lái)靈感。
總結(jié)
以上是生活随笔為你收集整理的阿里的easyexcal包实现表格动态导出的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【极简壁纸】桌面壁纸美图推荐_2019/
- 下一篇: 初学者基础教学篇一——握拍