EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
import java.util.HashMap;
import java.util.List;
public abstract class ExcelModel {
/**
* 验证参数有效性
* @return 返回验证结果
* @param args 参数
*/
public abstract boolean validation(String... args);
/**
* excel表头注释
* @return 返回注释信息
*/
public abstract HashMap<Integer, String> getAnnotationsMap();
/**
* excel 下拉选内容
* @return 返回下拉选信息
*/
public abstract HashMap<Integer, String[]> getDropDownMap();
/**
* 重点行
* @return 返回重点行信息
*/
public abstract List<Integer> getEmphasizeColumns();
}
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.StyleUtil;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.*;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;
@Slf4j
public class EasyExcelTitleHandler implements CellWriteHandler {
/**
错误信息处理时正则表达式的格式
*/
private final String EXCEL_ERROR_REG = "^(.*)(\\(错误:)(.*)(\\))$";
/**
操作列
*/
private final List<Integer> columnIndex;
/**
颜色
*/
private final Short colorIndex;
/**
批注<列的下标,批注内容>
*/
private final HashMap<Integer,String> annotationsMap;
/**
下拉框值
*/
private final HashMap<Integer,String[]> dropDownMap;
public EasyExcelTitleHandler(List<Integer> columnIndex, Short colorIndex, HashMap<Integer, String> annotationsMap, HashMap<Integer, String[]> dropDownMap) {
this.columnIndex = columnIndex;
this.colorIndex = colorIndex;
this.annotationsMap = annotationsMap;
this.dropDownMap = dropDownMap;
}
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
if(isHead){
// 设置列宽
Sheet sheet = writeSheetHolder.getSheet();
writeSheetHolder.getSheet().getRow(0).setHeight((short)(1.8*256));
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
Drawing<?> drawing = sheet.createDrawingPatriarch();
// 设置标题字体样式
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontName("宋体");
headWriteFont.setFontHeightInPoints((short)14);
headWriteFont.setBold(true);
if (CollectionUtils.isNotEmpty(columnIndex) &&
colorIndex != null &&
columnIndex.contains(cell.getColumnIndex())) {
// 设置字体颜色
headWriteFont.setColor(colorIndex);
}
headWriteCellStyle.setWriteFont(headWriteFont);
headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
CellStyle cellStyle = StyleUtil.buildHeadCellStyle(workbook, headWriteCellStyle);
cell.setCellStyle(cellStyle);
if (null != annotationsMap && annotationsMap.containsKey(cell.getColumnIndex())) {
// 批注内容
String context = annotationsMap.get(cell.getColumnIndex());
// 创建绘图对象
Comment comment=drawing.createCellComment(new XSSFClientAnchor(0, 0, 0,0, (short) cell.getColumnIndex(), cell.getRowIndex(), (short) cell.getColumnIndex()+2, (short) cell.getRowIndex()+2));
comment.setString(new XSSFRichTextString(context));
cell.setCellComment(comment);
}
if(null != dropDownMap && !dropDownMap.isEmpty() &&
dropDownMap.containsKey(cell.getColumnIndex())){
String[] data = dropDownMap.get(cell.getColumnIndex());
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
DataValidationConstraint dvConstraint = dvHelper.createExplicitListConstraint(data);
CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, cell.getColumnIndex(), cell.getColumnIndex());
DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);
validation.createPromptBox("提示", "只能选择下拉框里的值");
validation.setSuppressDropDownArrow(true);
validation.setShowPromptBox(true);
validation.setShowErrorBox(true);
validation.setEmptyCellAllowed(false);
sheet.addValidationData(validation);
dropDownMap.remove(cell.getColumnIndex());
}
}
// cell 匹配到错误信息规则,cell 字体设置为红色
Pattern pat = Pattern.compile(EXCEL_ERROR_REG);
if(pat.matcher(cell.getStringCellValue()).matches()) {
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
CellStyle cellStyle1 = workbook.createCellStyle();
Font cellFont = workbook.createFont();
assert colorIndex != null;
cellFont.setColor(colorIndex);
cellStyle1.setFont(cellFont);
cell.setCellStyle(cellStyle1);
}
}
}
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.xxx.ExcelModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EasyExcelListener<T extends ExcelModel> extends AnalysisEventListener<T> {
/**
* 用于保存正常的数据
*/
private List<T> data = Collections.synchronizedList(new ArrayList<>());
/**
* 用于保存异常数据
*/
private List<T> error = Collections.synchronizedList(new ArrayList<>());
private String[] arg;
@Override
public void invoke(T o, AnalysisContext analysisContext) {
if(o.validation(arg)) {
data.add(o);
}else {
error.add(o);
System.out.println("error--" + o);
}
}
/**
* excel 读取完之后的操作
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// analysisContext.
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public List<T> getError() {
return error;
}
public void setError(List<T> error) {
this.error = error;
}
public void setArg(String[] arg) {
this.arg = arg;
}
}
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.xxx.ExcelModel;
import org.apache.poi.ss.usermodel.IndexedColors;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
public class EasyExcelUtil<T extends ExcelModel> {
/**
* 导出excel
* @param outputStream 输出流
* @param dataList 导出的数据
* @param classT 模板类
* @param sheetName sheetName
* @param cellWriteHandlers 样式处理类
*/
public void writeExcelWithModel(OutputStream outputStream, List<T> dataList, Class<? extends ExcelModel> classT, String sheetName, CellWriteHandler... cellWriteHandlers) {
// 头的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 单元格策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 初始化表格样式
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
ExcelWriterSheetBuilder excelWriterSheetBuilder = EasyExcel.write(outputStream, classT).sheet(sheetName).registerWriteHandler(horizontalCellStyleStrategy);
if (null != cellWriteHandlers && cellWriteHandlers.length > 0) {
for (CellWriteHandler cellWriteHandler : cellWriteHandlers) {
excelWriterSheetBuilder.registerWriteHandler(cellWriteHandler);
}
}
// 开始导出
excelWriterSheetBuilder.doWrite(dataList);
}
/**
* 使用 模型 来读取Excel
* @param fileInputStream Excel的输入流
* @param clazz 模型的类
* @param arg 验证用的参数,可以没有
* @return 返回 模型 的列表(为object列表,需强转)
*/
public EasyExcelListener<T> readExcelWithModel(InputStream fileInputStream, Class<?> clazz, String... arg) {
EasyExcelListener<T> listener = new EasyExcelListener<>();
listener.setArg(arg);
ExcelReader excelReader = EasyExcel.read(fileInputStream, clazz, listener).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
excelReader.finish();
return listener;
}
public void createExcel(OutputStream os, List<T> data, T t, String sheetName) {
// 指定标红色的列
List<Integer> columns = t.getEmphasizeColumns();
// 指定批注
HashMap<Integer, String> annotationsMap = t.getAnnotationsMap();
// 指定下拉选
HashMap<Integer, String[]> dropDownMap = t.getDropDownMap();
EasyExcelTitleHandler easyExcelTitleHandler = new EasyExcelTitleHandler(columns, IndexedColors.RED.index, annotationsMap, dropDownMap);
writeExcelWithModel(os, data, t.getClass(), sheetName, easyExcelTitleHandler);
}
}
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.xxx.ExcelModel;
import com.xxx.EasyExcelListener;
import com.xxx.EasyExcelUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
public class ExcelWriteTest {
String[] clazzList = {"一年级1班", "一年级2班", "一年级3班"};
/**
* 测试excel导出
*/
@Test
public void excelExport() throws IOException {
// 输出流
OutputStream outputStream = new FileOutputStream("D:\\test.xlsx");
// 准备要导出的数据
List<TestModel> data = new ArrayList<>();
//获取excelUtil实例
EasyExcelUtil<TestModel> excelUtil = new EasyExcelUtil<>();
//设置导出时下拉选的内容
TestModel testModel = new TestModel();
//动态指定下拉选内容
testModel.setClazzList(clazzList);
//导出excel
try {
excelUtil.createExcel(outputStream, data, testModel, "用户导入模板");
}catch (Exception e) {
//导出失败的处理
e.printStackTrace();
}
}
/**
* 测试excel导入
*/
@Test
public void excelImport() throws FileNotFoundException {
InputStream inputStream;
inputStream = new FileInputStream("D:\\test.xlsx");
EasyExcelUtil<TestModel> easyExcelUtil = new EasyExcelUtil<>();
//导入excel,clazzList为动态指定的校验列表
EasyExcelListener<TestModel> listener =
easyExcelUtil.readExcelWithModel(inputStream, TestModel.class, clazzList);
System.out.println("格式正确的数据-data:----");
listener.getData().forEach(System.out::println);
System.out.println("格式异常的数据-error:----");
listener.getError().forEach(System.out::println);
}
@EqualsAndHashCode(callSuper = true)
@Data
public static class TestModel extends ExcelModel {
/**
* 动态指定下拉选的内容,或校验时的内容
*/
@ExcelIgnore
String[] clazzList;
/**
* excel导出不需要的属性
*/
@ExcelIgnore
private String id;
@ExcelProperty(value = {"班级", "班级"}, index = 0)
private String clazz;
/**
* value对应excel表头,index对应excel列数,第一列为 0
*/
@ExcelProperty(value = {"姓名", "姓名"}, index = 1)
private String name;
@ExcelProperty(value = {"年龄", "年龄"}, index = 2)
private String age;
/**
* 合并单元格的表头
*/
@ExcelProperty(value = {"成绩", "数学"}, index = 3)
//excel映射实体使用string字段,避免excel数据类型和java数据类型转换问题
private String scoreMath;
@ExcelProperty(value = {"成绩", "英语"}, index = 4)
private String scoreEnglish;
@Override
public boolean validation(String... args) {
//args为校验数据需要的变量,从listener中传入,如无需要可没有
boolean result = true;
//校验导入的数据
if(StringUtils.isBlank(this.getClazz()) || !Arrays.asList(args).contains(this.getClazz())) {
result = false;
//错误信息的格式需要对应 错误信息处理时正则表达式的格式
this.setClazz(this.getClazz() + "(错误:请使用下拉选的值或使用最新模板)");
}
return result;
}
@Override
public HashMap<Integer, String> getAnnotationsMap() {
// 指定批注
HashMap<Integer, String> annotationsMap = new HashMap<>();
annotationsMap.put(0, "必填,只能使用下拉选中的值");
return annotationsMap;
}
@Override
public HashMap<Integer, String[]> getDropDownMap() {
HashMap<Integer, String[]> dropDownMap = new HashMap<>();
// 指定下拉框
String[] classList = this.clazzList;
dropDownMap.put(0,classList);
return dropDownMap;
}
@Override
public List<Integer> getEmphasizeColumns() {
//标记比填列 —— excel并未做限制
return Arrays.asList(0, 1, 3, 4);
}
}
}
动态设置表头和单元格移步下一篇文章:easyExcel实现动态表头设置以及单元格样式设置