Excel导入导出详解
Excel导出重点代码详解
本位主要参考阿里开源EasyExcel
EasyExcel
参考 https://www.yuque.com/easyexcel/doc/easyexcel
POI
参考:https://poi.apache.org/components/
一、技术简介
1.1 技术背景
本文主要为了解决一些常见应用问题的参考示例和痛点说明
例如,复杂表头的处理,表格的样式处理,字体的样式处理,动态头等的数据处理。
1.2 技术选型
Excel读写时候内存溢出
POI是选择Excel解析最多的框架,但这个框架并不那么完美。大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。
- 这里选用EasyExcel能对存在大量数据时避免将全部全部数据一次加载到内存而是采用sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理。
二、项目搭建
2.1 快速开始
2.1.1 新建SpringBoot工程
2.1.2 引入核心依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.10</version>
</dependency>
2.2 读取Excel
2.2.1 基本读
- 数据模型
@Data
public class DemoBase {
private String string;
private Date date;
private Double doubleData;
}
- 读取的监听器
AnalysisEventListener<T>
- 读取的时候只需要继承AnalysisEventListener监听器后在
invoke
方法中获取相应的数据行进行业务处理即可
- 读取的时候只需要继承AnalysisEventListener监听器后在
@RequiredArgsConstructor
public class DemoExcelListener extends AnalysisEventListener<DemoData> {
/**
* 读取excel 一行一行读
* 第一行作为标题不会被读取
* @param demoData
* @param analysisContext
*/
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
//行数据
System.out.println(demoData.toString());
//其他业务操作
//-----------------
}
/**
* 读取完之后的执行操作
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//保存到数据库等
}
}
- 控制器
@RestController
@RequestMapping("/excel")
public class DemoController {
@PostMapping("/import")
public String upload(MultipartFile file) throws Exception {
try {
InputStream inputStream = file.getInputStream();
//需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
//EasyExcel.read(输入流,数据模型源,监听器)
EasyExcel.read(inputStream, DemoBase.class,new DemoExcelListener()).sheet().doRead();
} catch (IOException e) {
throw new Exception("文件读取失败,请重试!");
}
return "导入成功";
}
}
2.2.2 日期、数字或者自定义格式转换
有时Excel读取的内容不想按照原来的格式输出,而是按照自定义的格式通过一定规则进行读取。
①读Excel的性别字段值为
男
的,代表数据库字段为1
,字段值为男
的,代表数据库字段为0
②按照
yyyy年MM月dd日HH时mm分ss秒
格式接收日期③接收
#.##%
百分比的数字数据模型
@Data
public class DemoBase {
private String string;
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private Date date;
@NumberFormat("#.##%")
private Double doubleData;
@ExcelProperty(converter = CustomSexStringConverter.class)
private Integer gender;
}
转换器
Converter<T>
- 自定义转换器实现
Converter<T>
接口 - supportJavaTypeKey返回支持Java字段类型
- supportExcelTypeKey返回支持的表格读取类型
- convertToJavaData 读取时,数据转换具体实现
- 自定义转换器实现
public class CustomSexStringConverter implements Converter<Integer> {
@Override
public Class supportJavaTypeKey() {
return Integer.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
//读取的excel内容转换
@Override
public Integer convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return "男".equals(cellData.getStringValue()) ? 1 : 0;
}
//这里为写入excel方法,现在不用管
@Override
public CellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return new CellData(value.equals(1) ? "男" : "女");
}
}
- 监听器 参考2.2.1
- 控制器
@RestController
@RequestMapping("/excel")
@RequiredArgsConstructor
public class DemoController {
@CrossOrigin
@PostMapping("/import")
public ResponseModel<?> upload(MultipartFile file) throws Exception {
try {
InputStream inputStream = file.getInputStream();
//需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
//EasyExcel.read(输入流,数据模型源,监听器)
EasyExcel.read(inputStream, DemoBase.class,new DemoExcelListener(this)).sheet().doRead();
} catch (IOException e) {
throw new Exception("文件读取失败,请重试!");
}
return ResponseModel.ok();
}
}
2.2.3 指定列的下标或者列名
@Data
public class IndexOrNameData {
/**
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double doubleData;
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
}
2.2.4 多行头
headRowNumber
这里可以设置2,Excel这里的头设置为两行。不传入也可以,因为默认会根据DemoBase来解析,他没有指定头,也就是默认1
行
@RestController
@RequestMapping("/excel")
public class DemoController {
@PostMapping("/import")
public String upload(MultipartFile file) throws Exception {
try {
InputStream inputStream = file.getInputStream();
//需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
//EasyExcel.read(输入流,数据模型源,监听器)
EasyExcel.read(inputStream, DemoBase.class,new DemoExcelListener()).sheet().headRowNumber(2).doRead();
} catch (IOException e) {
throw new Exception("文件读取失败,请重试!");
}
return "导入成功";
}
}
2.2.5 数据异常转换处理
- 数据模型 参考2.2.2
- 监听器
onException
方法: 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
@Slf4j
public class DemoExcelListener extends AnalysisEventListener<DemoBase> {
@Override
public void invoke(DemoBase data, AnalysisContext context) {
//行数据
System.out.println(data.toString());
//其他业务操作
}
/**
* 读取完后的操作
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
//保存数据库等
}
/**
* 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
log.warn("解析失败,继续解析下一行数据。异常信息:{}",exception.getMessage());
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
log.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex());
}
}
}
- 控制器 参考2.2.2
2.3 写入Excel
2.3.1 基本写
- 数据模型
@Data
public class DemoWrite{
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
- 控制器
@RestController
@RequestMapping("/excel")
public class DemoController {
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DemoWrite.class).sheet("模板").doWrite(data());
}
//初始化数据
private List<DemoWrite> data() {
List<DemoWrite> list = new ArrayList<DemoWrite>();
for (int i = 0; i < 10; i++) {
DemoWrite data = new DemoWrite();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
}
2.3.2 指定写入的列
- 数据模型
@Data
public class DemoWrite {
@ExcelProperty(value = "字符串标题",index = 0)
private String string;
@ExcelProperty(value = "日期标题",index = 1)
private Date date;
/**
* 这里设置3 会导致第二列空的
*/
@ExcelProperty(value = "数字标题",index = 3)
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
- 控制器 参考2.3.1
2.3.3 复杂头的写入
- 数据模型
@Data
public class DemoWrite {
@ExcelProperty(value = {"主标题","字符串标题"})
private String string;
@ExcelProperty(value = {"主标题","日期标题"})
private Date date;
@ExcelProperty(value = {"主标题","数字标题"})
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
- 控制器 参考2.3.1
2.3.4 日期、数字或者自定义格式转换
有时Excel写入的内容不想按照原来的格式写入,而是按照自定义的格式通过一定规则进行写入。
①读取数据库的性别字段值为
0
的,代表实际含义为女
,读取数据库字段值为1
的,代表实际含义为男
②按照
yyyy年MM月dd日HH时mm分ss秒
格式写入日期③写入
#.##%
百分比的数字数据模型
@Data
public class DemoBase {
@ExcelProperty(value = "字符串标题")
private String string;
@ExcelProperty("日期标题")
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private Date date;
@ExcelProperty(value = "数字标题")
@NumberFormat("#.##%")
private Double doubleData;
@ExcelProperty(value = "性别",converter = CustomSexStringConverter.class)
private Integer gender;
}
转换器
Converter<T>
- 自定义转换器实现
Converter<T>
接口 - supportJavaTypeKey返回支持Java字段类型
- supportExcelTypeKey返回支持的表格读取类型
- convertToExcelData 写入时,数据转换具体实现
- 自定义转换器实现
public class CustomSexStringConverter implements Converter<Integer> {
@Override
public Class supportJavaTypeKey() {
return Integer.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
//这里为读取的处理方法,不用管
@Override
public Integer convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return "男".equals(cellData.getStringValue()) ? 1 : 0;
}
@Override
public CellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return new CellData(value.equals(1) ? "男" : "女");
}
}
- 控制器
@RestController
@RequestMapping("/excel")
public class DemoController {
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DemoBase.class).sheet("模板").doWrite(dataInit());
}
//初始化数据
private List<DemoBase> dataInit() {
List<DemoBase> list=new ArrayList<>();
for (int i = 0; i < 10; i++) {
DemoBase data = new DemoBase();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
data.setGender(i%2);
list.add(data);
}
return list;
}
}
2.3.5 指定列宽列高
ContentRowHeight
正文内容的表格高度HeadRowHeight
头的表格高度- 在类上
ColumnWidth
表示头的表格宽度 - 在属性上
ColumnWidth
表示正文内容的表格宽度
@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
/**
* 宽度为50
*/
@ColumnWidth(50)
@ExcelProperty("数字标题")
private Double doubleData;
}
2.3.6 给指定单元格添加下拉选项
- 数据模型
@Data
public class DemoBase {
@ExcelProperty(value = "字符串标题")
private String string;
@ExcelProperty("日期标题")
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private Date date;
@ExcelProperty(value = "数字标题")
@NumberFormat("#.##%")
private Double doubleData;
@ExcelProperty(value = "性别",converter = CustomSexStringConverter.class)
private Integer gender;
@ExcelProperty(value = "合格")
private String isPass;
}
转换器 参考2.3.4
添加
SheetWriteHandler
拦截器
只需要继承
SheetWriteHandler
接口实现afterSheetCreate
方法即可afterSheetCreate
具体的实现方法:CellRangeAddressList
表示添加的单元格位置,有四个构造参数,分别代表
①行索引开始位置 ② 行索引结束位置 ③列索引开始位置 ④列索引结束位置
这里代表从第2行开始到11行结束,第5列开始到第5列结束生成下拉框
public class MySheetWriteHandler implements SheetWriteHandler {
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 10, 4, 4);
DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper();
DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"是", "否"});
DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
//默认把显示的值以下拉框显示d
dataValidation.setSuppressDropDownArrow(false);
writeSheetHolder.getSheet().addValidationData(dataValidation);
}
}
- 控制器
- 这里只需要在写入之前在
registerWriteHandler
方法中注册自定义的拦截器即可
- 这里只需要在写入之前在
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DemoBase.class).sheet("模板").registerWriteHandler(new MySheetWriteHandler()).doWrite(dataInit());
}
2.3.7 锁定指定单元格
数据模型 参考2.3.6
转换器 参考 2.3.4
添加拦截器
CellWriteHandler
这里只需要实现
CellWriteHandler
接口中的afterCellDispose
方法即可afterCellDispose
代表数据写完到表格之后的操作先设置所有单元格锁定,并设置密码为
111
,然后获取当前行的索引是否不为0(0代表标题行),然后再判断列是否在实际的索引范围内,若是则设定单元格格式为非锁定。这里还添加一个额外的动态样式设定,当索引为3,即为第四列时,若值为男
则设置表格颜色为红色
public class MyCellWriteHandler implements CellWriteHandler {
@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 afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, 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) {
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
Sheet sheetAt = workbook.getSheetAt(0);
sheetAt.protectSheet("111");
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setLocked(true);
//索引从0开始
int columnIndex = cell.getColumnIndex();
int rowIndex= cell.getRowIndex();
if (rowIndex != 0) {
if (columnIndex <= 4) {
CellStyle noneLock = workbook.createCellStyle();
noneLock.setLocked(false);
if (columnIndex == 3) {
String stringCellValue = cell.getStringCellValue();
if (StringUtils.equals(stringCellValue, "男")) {
Font font = workbook.createFont();
font.setColor(IndexedColors.RED.index);
noneLock.setFont(font);
}else{
noneLock.setFont(null);
}
}
noneLock.setAlignment(HorizontalAlignment.CENTER);
cell.setCellStyle(noneLock);
}
}
}
}
- 控制器
- 只需在
registerWriteHandler
中注册新的MyCellWriteHandler
自定义拦截器即可
- 只需在
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DemoBase.class).sheet("模板").registerWriteHandler(new MySheetWriteHandler()).registerWriteHandler(new MyCellWriteHandler()).doWrite(dataInit());
}
- 结果
- 标题和正文内容外不可编辑,需要编辑密码
2.3.8 限制只能输入指定值并提示
- 数据模型
@Data
public class DemoBase {
@ExcelProperty(value = "字符串标题")
private String string;
@ExcelProperty("日期标题")
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private Date date;
@ExcelProperty(value = "数字标题")
@NumberFormat("#.##%")
private Double doubleData;
@ExcelProperty(value = "性别",converter = CustomSexStringConverter.class)
private Integer gender;
@ExcelProperty(value = "合格")
private String isPass;
@ExcelProperty(value = "测试列")
private String testCell;
}
拦截器
SheetWriteHandler
- 这里对数据值只能输入【是】或者【否】的值,否则进行提示
public class MySheetWriteHandler implements SheetWriteHandler {
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
//这里对 第1行到第10行 第5列做限制
CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 10, 4, 4);
DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper();
//固定值
DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"是", "否"});
DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
//设置固定的值不可见,非下拉
dataValidation.setSuppressDropDownArrow(false);
//提示框的类型
dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
//提示框内容
dataValidation.createErrorBox("错误提示", "只能输入【是】或者【否】");
//展示提示框
dataValidation.setShowErrorBox(true);
//用户输入时的焦点提示
dataValidation.createPromptBox("温馨提示", "只能输入【是】或者【否】");
//展示提示框
dataValidation.setShowPromptBox(true);
writeSheetHolder.getSheet().addValidationData(dataValidation);
}
}
2.3.9 限制输入数字范围
拦截器
SheetWriteHandler
- 这里限制100-200的整数
public class MySheetWriteHandler implements SheetWriteHandler {
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
//这里对 第1行到第10行 第6列做限制
CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 10, 5, 5);
DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper();
DataValidationConstraint constraint = helper.createNumericConstraint(DVConstraint.ValidationType.INTEGER, DVConstraint.OperatorType.BETWEEN, "100", "200");
DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
dataValidation.setSuppressDropDownArrow(false);
dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
dataValidation.createErrorBox("错误提示", "只能输入100-200之间的整数");
dataValidation.setShowErrorBox(true);
dataValidation.createPromptBox("温馨提示", "只能输入100-200之间的整数");
dataValidation.setShowPromptBox(true);
writeSheetHolder.getSheet().addValidationData(dataValidation);
}
}
2.3.10 各种类型输入限制说明
经过追踪源码发现,在DataValidationHelper
接口中已经定义了所有常用验证器,而其内部实现都是通过DVConstraint
实现的
- 在使用时,只需要创建不同的验证器即可
DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper();
DataValidationConstraint constraint = helper.createNumericConstraint(DVConstraint.ValidationType.INTEGER, DVConstraint.OperatorType.BETWEEN, "100", "200");
常用验证器说明:
createNumericConstraint 数字校检器
createTextLengthConstraint 文本长度校检器
createTimeConstraint 时间校检器
createCustomConstraint 自定义校检器
createExplicitListConstraint 固定值校检器
这里createExplicitListConstraint() 方法可以传递包含整数、浮点、日期或文本值的字符串数组。
public interface DataValidationHelper {
DataValidationConstraint createFormulaListConstraint(String listFormula);
DataValidationConstraint createExplicitListConstraint(String[] listOfValues);
DataValidationConstraint createNumericConstraint(int validationType,int operatorType, String formula1, String formula2);
DataValidationConstraint createTextLengthConstraint(int operatorType, String formula1, String formula2);
DataValidationConstraint createDecimalConstraint(int operatorType, String formula1, String formula2);
DataValidationConstraint createIntegerConstraint(int operatorType, String formula1, String formula2);
DataValidationConstraint createDateConstraint(int operatorType, String formula1, String formula2,String dateFormat);
DataValidationConstraint createTimeConstraint(int operatorType, String formula1, String formula2);
DataValidationConstraint createCustomConstraint(String formula);
DataValidation createValidation(DataValidationConstraint constraint,CellRangeAddressList cellRangeAddressList);
}
2.3.11 三种拦截器执行属性参考
执行顺序如下:
① SheetWriteHandler中的**beforeSheetCreate->afterSheetCreate
创建第一个工作表之前的操作--> 创建第一个工作表之后的操作
②RowWriteHandler中的**beforeRowCreate->afterRowCreate
创建第一行表格之前的操作-->创建第一行表格之后的操作
③CellWriteHandler中的**beforeCellCreate->afterCellCreate->afterCellDispose
创建第一行第一列单元格之前的操作--> 创建第一行第一列单元格之后的操作-->创建第一行第一列内容后的操作
④RowWriteHandler中的**afterRowDispose
创建第一行数据后的操作