这段时间因为公司业务需求,需要每天、每周为运营人员做个数据统计,需要按照给定的模板,定时将数据导出并发送给指定的人,如果纯粹用POI去做的话,生成指定模板格式的excel会很复杂,所以在玩网上搜了一下,看看有没有好的现成的工具,结果让我发现的一个非常好用又非常简单的工具,那就是jxls,刚开始用的jxls1.x,但是功能还不能满足我的所有要求,所以后来升级换成了现在使用的jxls2.x版本,现在将jxls2.x的使用通过博客记录下来,以后如果还需要用到,直接看自己写的博客就可以了。
jxls使用非常简单,只需要事先做好需要生成的excel样式,再加入jxls的指令和表达式即可做成模板文件,根据模板文件可以原样输出excel文件,特别是对于复杂样式的模板,简直不要太爽~~~
jxls
官网地址:http://jxls.sourceforge.net/index.html
最开始是按照李狐同学的博客:https://blog.csdn.net/sinat_15769727/article/details/78898894 完成的第一版,但是没有合并单元格的功能,之后找到 lnktoking 写的开源jxls 增强版 jxlss:https://gitee.com/lnkToKing/jxlss,将其中新功能之一的合并单元格功能整合到自己的项目中,非常感谢两位大神的贡献。
话不多说,接下来直接上代码
首先导入jxls的依赖:
org.jxls
jxls
2.4.6
org.jxls
jxls-poi
1.0.12
org.jxls
jxls-jexcel
1.0.6
使用的jxls的版本是2.4.6,jxls-poi的版本是1.0.12,有了这两个依赖就可以完成excel的导出了,但是如果需要动态合并单元格的话就需要导入jxls-jexcel的依赖
站在巨人的肩膀上,导出使用的封装工具类:
package com.xixi.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.jxls.builder.xls.XlsCommentAreaBuilder;
import org.jxls.common.Context;
import org.jxls.expression.JexlExpressionEvaluator;
import org.jxls.transform.Transformer;
import org.jxls.transform.poi.PoiTransformer;
import org.jxls.util.JxlsHelper;
public class JxlsUtils{
static{
//添加自定义指令(可覆盖jxls原指令)
//合并单元格(模板已经做过合并单元格操作的单元格无法再次合并)
XlsCommentAreaBuilder.addCommandMapping("merge", MergeCommand.class);
}
public static void exportExcel(InputStream is, OutputStream os, Map
model) throws IOException{
Context context = PoiTransformer.createInitialContext();
if (model != null) {
for (Map.Entry entry : model.entrySet()){
context.putVar(entry.getKey(), entry.getValue());
}
}
JxlsHelper jxlsHelper = JxlsHelper.getInstance();
Transformer transformer = jxlsHelper.createTransformer(is, os);
//获得配置
JexlExpressionEvaluator evaluator = (JexlExpressionEvaluator)transformer.getTransformationConfig().getExpressionEvaluator();
//设置静默模式,不报警告
evaluator.getJexlEngine().setSilent(true);
//函数强制,自定义功能
Map funcs = new HashMap();
funcs.put("jx", new JxlsUtils()); //添加自定义功能
evaluator.getJexlEngine().setFunctions(funcs);
//必须要这个,否者表格函数统计会错乱
jxlsHelper.setUseFastFormulaProcessor(false).processTemplate(context, transformer);
}
public static void exportExcel(File xls, File out, Map model) throws FileNotFoundException, IOException {
exportExcel(new FileInputStream(xls), new FileOutputStream(out), model);
}
public static void exportExcel(String templatePath, OutputStream os, Map model) throws Exception {
File template = getTemplate(templatePath);
if(template != null){
exportExcel(new FileInputStream(template), os, model);
} else {
throw new Exception("Excel 模板未找到。");
}
}
//获取jxls模版文件
public static File getTemplate(String path){
File template = new File(path);
if(template.exists()){
return template;
}
return null;
}
// 日期格式化
public String dateFmt(Date date, String fmt) {
if (date == null) {
return "";
}
try {
SimpleDateFormat dateFmt = new SimpleDateFormat(fmt);
return dateFmt.format(date);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
// if判断
public Object ifelse(boolean b, Object o1, Object o2) {
return b ? o1 : o2;
}
}
合并单元格功能实现:
package com.xixi.demo;
import jxl.write.WriteException;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.jxls.area.Area;
import org.jxls.command.AbstractCommand;
import org.jxls.command.Command;
import org.jxls.common.CellRef;
import org.jxls.common.Context;
import org.jxls.common.Size;
import org.jxls.transform.Transformer;
import org.jxls.transform.jexcel.JexcelTransformer;
import org.jxls.transform.poi.PoiCellData;
import org.jxls.transform.poi.PoiTransformer;
/**
* 合并单元格
* jx:merge(
* lastCell="单元格"
* [, cols="合并的列数"]
* [, rows="合并的行数"]
* [, minCols="最小合并的列数"]
* [, minRows="最小合并的行数"]
* )
* @author Victor
* @date 2018年12月6日
*/
public class MergeCommand extends AbstractCommand{
private String cols; //合并的列数
private String rows; //合并的行数
private String minCols; //最小合并的列数
private String minRows; //最小合并的行数
private CellStyle cellStyle;//第一个单元格的样式
private Area area;
@Override
public String getName() {
return "merge";
}
@Override
public Command addArea(Area area) {
if (super.getAreaList().size() >= 1) {
throw new IllegalArgumentException("You can add only a single area to 'merge' command");
}
this.area = area;
return super.addArea(area);
}
@Override
public Size applyAt(CellRef cellRef, Context context) {
int rows = area.getSize().getHeight();
int cols = area.getSize().getWidth();
rows = Math.max(getVal(this.rows, context), rows);
cols = Math.max(getVal(this.cols, context), cols);
rows = Math.max(getVal(this.minRows, context), rows);
cols = Math.max(getVal(this.minCols, context), cols);
if(rows > 1 || cols > 1){
Transformer transformer = this.getTransformer();
if(transformer instanceof PoiTransformer){
poiMerge(cellRef, context, (PoiTransformer)transformer, rows, cols);
}else if(transformer instanceof JexcelTransformer){
jexcelMerge(cellRef, context, (JexcelTransformer)transformer, rows, cols);
}
}
area.applyAt(cellRef, context);
return new Size(cols, rows);
}
protected Size poiMerge(CellRef cellRef, Context context, PoiTransformer transformer, int rows, int cols){
Sheet sheet = transformer.getWorkbook().getSheet(cellRef.getSheetName());
CellRangeAddress region = new CellRangeAddress(
cellRef.getRow(),
cellRef.getRow() + rows - 1,
cellRef.getCol(),
cellRef.getCol() + cols - 1);
sheet.addMergedRegion(region);
//合并之后单元格样式会丢失,以下操作将合并后的单元格恢复成合并前第一个单元格的样式
area.applyAt(cellRef, context);
if(cellStyle == null){
PoiCellData cellData = (PoiCellData)transformer.getCellData(area.getStartCellRef());
if(cellData != null){
cellStyle = cellData.getCellStyle();
}
}
setRegionStyle(cellStyle, region, sheet);
return new Size(cols, rows);
}
protected Size jexcelMerge(CellRef cellRef, Context context, JexcelTransformer transformer, int rows, int cols){
try {
transformer.getWritableWorkbook().getSheet(cellRef.getSheetName())
.mergeCells(
cellRef.getRow(),
cellRef.getCol(),
cellRef.getRow() + rows - 1 ,
cellRef.getCol() + cols - 1);
area.applyAt(cellRef, context);
} catch (WriteException e) {
throw new IllegalArgumentException("合并单元格失败");
}
return new Size(cols, rows);
}
private static void setRegionStyle(CellStyle cs, CellRangeAddress region, Sheet sheet) {
for (int i = region.getFirstRow(); i <= region.getLastRow(); i++) {
Row row = sheet.getRow(i);
if (row == null) {
row = sheet.createRow(i);
}for (int j = region.getFirstColumn(); j <= region.getLastColumn(); j++) {
Cell cell = row.getCell(j);
if (cell == null) {
cell = row.createCell(j);
}
if (cs == null){
cell.getCellStyle().setAlignment(CellStyle.ALIGN_CENTER);
cell.getCellStyle().setVerticalAlignment(CellStyle.VERTICAL_CENTER);
}else {
cell.setCellStyle(cs);
}
}
}
}
private int getVal(String expression, Context context){
if(StringUtils.isNotBlank(expression)){
Object obj = getTransformationConfig().getExpressionEvaluator().evaluate(expression, context.toMap());
try {
return Integer.parseInt(obj.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("表达式:" + expression + " 解析失败");
}
}
return 0;
}
public String getCols() {
return cols;
}
public void setCols(String cols) {
this.cols = cols;
}
public String getRows() {
return rows;
}
public void setRows(String rows) {
this.rows = rows;
}
public String getMinCols() {
return minCols;
}
public void setMinCols(String minCols) {
this.minCols = minCols;
}
public String getMinRows() {
return minRows;
}
public void setMinRows(String minRows) {
this.minRows = minRows;
}
}
其中:
好了,工具封装好了,那接下来就是需要导出的数据以及根据数据格式写好excel模板,也不一个一个功能单独说了,想一步一步来可以去看李狐同学的教程, 直接上一个综合的demo
数据准备&&main方法:
package com.xixi.demo;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class App
{
public static void main( String[] args ) throws Exception {
List students = new ArrayList<>();
Student student = new Student();
student.setName("张三");
student.setGender("男");
student.setGradeClass("初一一班");
List courses = new ArrayList<>();
Course course = new Course();
course.setCourseName("语文");
course.setCourseScore("98");
courses.add(course);
course = new Course();
course.setCourseName("数学");
course.setCourseScore("105");
courses.add(course);
course = new Course();
course.setCourseName("物理");
course.setCourseScore("80");
courses.add(course);
student.setCourses(courses);
students.add(student);
student = new Student();
student.setName("王丽丽");
student.setGender("女");
student.setGradeClass("初一二班");
courses = new ArrayList<>();
course = new Course();
course.setCourseName("语文");
course.setCourseScore("102");
courses.add(course);
course = new Course();
course.setCourseName("数学");
course.setCourseScore("110");
courses.add(course);
student.setCourses(courses);
students.add(student);
student = new Student();
student.setName("李梅");
student.setGender("女");
student.setGradeClass("初一三班");
courses = new ArrayList<>();
course = new Course();
course.setCourseName("语文");
course.setCourseScore("110");
courses.add(course);
course = new Course();
course.setCourseName("数学");
course.setCourseScore("100");
courses.add(course);
course = new Course();
course.setCourseName("物理");
course.setCourseScore("85");
courses.add(course);
student.setCourses(courses);
students.add(student);
//模板里展示的数据
Map data = new HashMap<>();
data.put("students", students);
// 模板路径和输出流
String templatePath = "E:\\jxls\\studentTemplate.xlsx";
OutputStream os = new FileOutputStream("E:\\jxls\\student.xls");
//调用封装的工具类,传入模板路径,输出流,和装有数据的Map,按照模板导出
JxlsUtils.exportExcel(templatePath, os, data);
os.close();
}
}
Student类:
package com.xixi.demo;
import java.util.ArrayList;
import java.util.List;
/**
* @Auther: Victor
* @Date: 2018/12/6 11:04
* @Description:
*/
public class Student {
private String name;
private String gender;
private String gradeClass;
private List courses = new ArrayList<>();
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", gender='" + gender + '\'' + ", gradeClass='" + gradeClass + '\'' + ", courses=" + courses + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getGradeClass() {
return gradeClass;
}
public void setGradeClass(String gradeClass) {
this.gradeClass = gradeClass;
}
public List getCourses() {
return courses;
}
public void setCourses(List courses) {
this.courses = courses;
}
}
Course类:
package com.xixi.demo;
/**
* @Auther: Victor
* @Date: 2018/12/6 11:08
* @Description:
*/
public class Course {
private String courseName;
private String courseScore;
@Override
public String toString() {
return "Course{" + "courseName='" + courseName + '\'' + ", courseScore='" + courseScore + '\'' + '}';
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getCourseScore() {
return courseScore;
}
public void setCourseScore(String courseScore) {
this.courseScore = courseScore;
}
}
最后导出模板studentTemplate.xlsx:
模板的注解含义在这里就不多说了,直接看官网或者看上面两位大神的介绍,注解中的items就是数据data中的key,表达式${}中的值就是data中数据对象的属性
好了,模板写好了,直接执行上面的main方法
导出结果:
怎么样,简单吧,比起直接用POI导出工作量不知道减少多少
友情提示:
demo展示的是纵向循环数据输出,横向循环输出也是可以的,需要在注解里加一个属性direction="RIGHT"就行了:
jx:each(items="students" var="student" lastCell="E3" direction="RIGHT")
横向输出demo这里就不再陈述了,可以自行去实践研究,导出图片和其他功能可以参阅开源项目jxlss
最后,这是笔者第一次写的第一篇博客,写得不清楚的地方还望各位看官见谅,有什么错误的地方欢迎指正,大家一起学习,共同进步,由于篇幅的问题,这里写的是综合多个功能点的demo,想看详细的教程请移步上面两位大神的博客或者开源项目中详阅。
demo源码就不贴了,上面展示的就是所有源码
参考博客:
https://blog.csdn.net/sinat_15769727/article/details/78898894
https://blog.csdn.net/lnktoking/article/details/79195500