在开发中遇到了一个第一列需要合并单元格 , 然后和后面的列形成父子关系 , 并需要返回一个树形结构的实体类的需求 , 可以使用AnalysisEventListener回调监听器实现
一. AnalysisEventListener简介
在 Java 里,AnalysisEventListener
是阿里巴巴开源项目 EasyExcel 中的一个关键类,它主要用于在解析 Excel 文件时处理数据。
1. 所属库
AnalysisEventListener
属于 EasyExcel 库,该库是一个基于 Java 的简单、省内存的读写 Excel 工具,在处理大数据量的 Excel 文件时优势显著。
2. 类的作用
AnalysisEventListener
是一个抽象类,你可以通过继承它来实现自定义的监听器,在 Excel 文件解析过程中对数据进行处理。当 EasyExcel 逐行解析 Excel 文件时,会触发监听器中的相应方法,让你能够对每一行数据进行处理。
3. 常用方法及其用途
invoke(T data, AnalysisContext context)
- 功能:每当解析到一行数据时,此方法就会被调用。
data
是当前行解析后的数据对象,context
包含了解析过程中的上下文信息。 - 示例:
@Override
public void invoke(MyData data, AnalysisContext context) {
// 处理每一行数据
System.out.println("解析到一行数据:" + data);
}
doAfterAllAnalysed(AnalysisContext context)
- 功能:当整个 Excel 文件解析完毕后,该方法会被调用。你可以在这个方法中进行一些收尾工作,比如保存数据到数据库、输出统计信息等。
- 示例:
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 所有数据解析完成,进行收尾工作
System.out.println("Excel 文件解析完成");
}
extra(CellExtra extra, AnalysisContext context)
- 功能:当解析到一些额外信息(如合并单元格、超链接、批注等)时,此方法会被调用。
extra
包含了额外信息的具体内容。 - 示例:
@Override
public void extra(CellExtra extra, AnalysisContext context) {
if (extra.getType() == CellExtraTypeEnum.MERGE) {
// 处理合并单元格信息
System.out.println("发现合并单元格:" + extra);
}
}
4. 使用步骤
步骤 1:引入依赖
在 Maven 项目中,你需要在 pom.xml
文件里添加 EasyExcel 的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
步骤 2:创建数据类
创建一个 Java 类来映射 Excel 文件中的数据:
@Data
public class ParentChiled{
private String name;
private Integer sex;
}
步骤 3:创建监听器
继承 AnalysisEventListener
类,实现自定义监听器:
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
public class MyDataListener extends AnalysisEventListener<ParentChiled> {
private List<ParentChiled> dataList = new ArrayList<>();
@Override
public void invoke(MyData data, AnalysisContext context) {
dataList.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 处理所有数据
System.out.println("共解析到 " + dataList.size() + " 条数据");
}
public List<MyData> getDataList() {
return dataList;
}
}
二. 处理合并单元格的实例
1.接收Excel文件,在ServiceImpl层进行处理
@Override
public Map<String, Object> readExcel(MultipartFile file) {
try(InputStream inputStream = file.getInputStream()) {
MergedExcelListener listener = new MergedExcelListener();
EasyExcel.read(inputStream)
.extraRead(CellExtraTypeEnum.MERGE)// 启用合并单元格解析
.registerReadListener(listener)
.sheet()
.headRowNumber(2) // 跳过表头
.doRead();
List<Parent> result = listener.getResult();
return ReturnMapUtil.getSuccessMap(result);
}catch (Exception e) {
e.printStackTrace();
return ReturnMapUtil.getErrorMapWithMessage(e.getMessage());
}
}
2. 继承AnalysisEventListener 处理Excel
public class MergedExcelListener extends AnalysisEventListener<Map<Integer, String>> {
private List<Parent> result = new ArrayList<>();
private List<CellExtra> mergedRegions = new ArrayList<>();
private List<Map<Integer, String>> rawDataRows = new ArrayList<>(); // 存储原始数据行
private List<Integer> rowIndexes = new ArrayList<>(); // 存储每行的索引
@Override
public void extra(CellExtra extra, AnalysisContext context) {
if (extra.getType() == CellExtraTypeEnum.MERGE) {
mergedRegions.add(extra);
}
}
@Override
public void invoke(Map<Integer, String> dataMap, AnalysisContext context) {
// 暂存原始数据和行号
rawDataRows.add(dataMap);
rowIndexes.add(context.readRowHolder().getRowIndex());
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 所有数据收集完毕后,统一处理
processMergedData();
}
private void processMergedData() {
Parent currentParent = null;
// 遍历每一行原始数据
for (int i = 0; i < rawDataRows.size(); i++) {
Map<Integer, String> dataMap = rawDataRows.get(i);
int rowIndex = rowIndexes.get(i);
int mergedColumn = 0; // 合并列索引
// 判断是否为合并起始行
boolean isMergeStart = isMergeStartRow(rowIndex, mergedColumn);
String firstColumnValue = dataMap.get(mergedColumn);
// 如果是合并起始行或者第一列有新值,创建新的父对象
if (isMergeStart || (firstColumnValue != null && !firstColumnValue.isEmpty() && (currentParent == null ||!firstColumnValue.equals(currentParent.getJcxm())))) {
currentParent = new Parent();
currentParent.setParentName(firstColumnValue);
currentParent.setChiledList(new ArrayList<>());
result.add(currentParent);
}
// 将当前行数据添加到最近的父对象
if (currentParent != null) {
ParentChiled child = new ParentChiled();
child.setName(dataMap.get(1)); // 子列1
child.setSex(dataMap.get(2)); // 子列2
currentParent.getChiledList().add(child);
}
}
}
// 判断当前行是否为某个合并区域的起始行
private boolean isMergeStartRow(int row, int column) {
for (CellExtra merge : mergedRegions) {
if (merge.getFirstRowIndex() == row
&& merge.getFirstColumnIndex() <= column
&& merge.getLastColumnIndex() >= column) {
return true;
}
}
return false;
}
public List<Parent> getResult() {
return result;
}
}
3. 使用
比如说这种格式的Excel导入就会获得
[
{
"parentName": "组长",
"chiledList": [
{
"name": "张三",
"sex": "男"
},
{
"name": "李四",
"sex": "女"
}
]
},
{
"parentName": "总经理",
"chiledList": [
{
"name": "赵六",
"sex": "男"
}
]
}
]