EasyExcel read file detailed explanation and source code analysis

When reading and importing files, we often see the following methods.

//同步的返回,不推荐使用
EasyExcel.read(file).sheet(sheetNo).head(Class.class).headRowNumber(headRowNum)..doReadSync(); 

//异步的,通过监听器处理读到的数据。
EasyExcel.read(file).sheet(sheetNo).head(Class.class).headRowNumber(headRowNum).registerReadListener(监听器)doRead(); 

First of all, I suggest that you can take a look at the source code related to the EasyExcelFactory factory class provided by EasyExcel. If you understand the source code, it is very useful for us to write methods suitable for our own projects.

Import dependencies:

        <!--   easyexcel 3.1.0+版本不需要poi依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.1.1</version>
        </dependency>

1. EasyExcel read file source code

1. EasyExcelFactory factory class

Method: EasyExcel.read(file)

Check out the read() method:

insert image description here

The EasyExcel class calls the read method, which actually calls the method of the EasyExcelFactory class.

Check out the EasyExcelFactory class method:

insert image description here

The EasyExcelFactory factory class defines many overloaded methods for reading and writing. There are mainly two types of reading methods:

  • Read the Excel file and return it ExcelWriterBuilder类.
  • Read the sheet in the Excel file and return it ExcelReaderSheetBuilder类.

From here we can see that the EasyExcelFactory factory class created the XxxBuilder class and returned the XxxBuilder class, so can we also directly use the XxxBuilder class to operate and read files. The answer is yes.

2. ExcelWriterBuilder class

Check out the ExcelReaderBuilder class:

insert image description here

When the ExcelReaderBuilder class is instantiated, a ReadWorkbook object is created.

Check out the ExcelReaderBuilder class methods:

insert image description here

The ExcelReaderBuilder class mainly processes Excel files and related file attribute information, such as setting character encoding, file encryption passwords, ignoring which row of data to process, etc.

3. ExcelReaderSheetBuilder class

Check out the ExcelReaderSheetBuilder class:

insert image description here

When the ExcelReaderSheetBuilder class is instantiated, a ReadSheet object and an ExcelReader object are created.

Check out the ExcelReaderSheetBuilder class methods:

insert image description here

The ExcelReaderSheetBuilder class mainly processes each sheet information in the Excel file, such as setting the sheet name and index to be read.

调用 doRead()方法其实底层通过 ExcelReader对象读取每一个 sheet信息。

4. ExcelReader class

Check out the ExcelReader class:

insert image description here

View ExcelReader class methods:

insert image description here
Here we mainly look at the read method.

insert image description here

It can be seen that the ExcelReader class contains the ReadWorkbook object and the ReadSheet object. In this way, each sheet information in the Excel file is processed.

思考:

(1) How is the ExcelReader class initialized?

When the ExcelReaderBuilder class calls the sheet() method, the ExcelReader object is initialized.

insert image description here

When the ExcelReaderSheetBuilder class calls the doRead() method, the bottom layer traverses and reads each sheet information through the ExcelReader object.

insert image description here

(2) How are ReadWorkbook objects and ReadSheet objects assigned to the ExcelReader class?

When initialized by the constructor in the respective XxxBuilder class, it is created, and then the assignment is completed in the respective build() methods of calling the sheet() method and the doRead() method.

With the above knowledge, it is relatively simple to read Excel files through EasyExcel operations.

Second, read a sheet

In general, we create an object to establish a mapping relationship with the column names of the Excel file sheet.

Specify the subscript or column name of the column:

  • Adding on a field @ExcelProperty注解, it is not recommended to use index and name at the same time, either an object only uses index, or an object only uses name to match.

Custom format conversion:

  • You can customize the format converter, or use the built-in date and number format conversion.

Below we create an Excel file.

insert image description here

Create a mapping class based on Sheet information:

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    
    

    @ExcelProperty("标题")
    private String string;

    @ExcelProperty("日期")
    private Date date;

    @ExcelProperty("浮点数据")
    private Double doubleData;

}

1. Synchronous read

Read and return data synchronously, not recommended.

    public static void main(String[] args) {
    
    
        syncRead();
    }

    private static void syncRead() {
    
    
        String fileName = "D:\\TempFiles\\表格.xlsx";
        List<DemoData> demoDataList = EasyExcel.read(fileName)
                .sheet()
                .head(DemoData.class)
                .headRowNumber(1)
                .doReadSync(); //同步读
        log.info("同步解析到所有数据为:{}", JSON.toJSONString(demoDataList));
    }

2. Asynchronous read

Register a built-in anonymous listener.

    public static void asyncRead() {
    
    
        String fileName = "D:\\TempFiles\\表格.xlsx";
        EasyExcel.read(fileName)// 读取Excel文件
                .sheet(0) // 读取哪个sheet,索引从0开始
                .head(DemoData.class) // 设置映射对象
                .headRowNumber(1) // 设置1,因为头值占了一行。如果多行头,就设置几行。索引从1开始
                .registerReadListener(new AnalysisEventListener<DemoData>() {
    
     //注册读的监听器
                    /**
                     * 每解析一行excel数据,就会被调用一次
                     * @param demoData
                     * @param analysisContext
                     */
                    @Override
                    public void invoke(DemoData demoData, AnalysisContext analysisContext) {
    
    
                        log.info("解析到一条数据为:{}", JSON.toJSONString(demoData));
                    }

                    /**
                     * 全部解析完被调用
                     * @param analysisContext
                     */
                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    
    
                        log.info("全部解析完成");
                    }
                })
                .doRead();
    }

insert image description here

3. Read multiple sheets

The listener can be understood as a logical part of checking (null checking, type checking) and processing the read data for asynchronous reading.

AnalysisEventListener<T> excelListenerIn the code in the asynchronous reading above, a listener is used as a parameter .

The AnalysisEventListener class implements the ReadListener interface, and the ReadListener has the following methods:

public interface ReadListener<T> extends Listener {
    
    
 
    // 在转换异常获取其他异常下会调用本接口。
    default void onException(Exception exception, AnalysisContext context) throws Exception {
    
    
        throw exception;
    }
 
    //读取表头数据存在headMap中
    default void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
    
    }
 
    //读取一行一行数据到data
    void invoke(T data, AnalysisContext context);
 
    void extra(CellExtra var1, AnalysisContext context);
 
    //在完成所有数据解析后进行的操作。AOP思想。
    void doAfterAllAnalysed(AnalysisContext context);
 
    default boolean hasNext(AnalysisContext context) {
    
    
        return true;
    }
}

We can extend the AnalysisEventListener class or the ReadListener interface according to our own needs, and extend those methods.

In the above Excel file, we create another Sheet.

insert image description here

Correspondingly create a mapping class:

@Getter
@Setter
@EqualsAndHashCode
public class DemoData2 {
    
    

    @ExcelProperty("标题")
    private String string;

    @ExcelProperty("日期")
    private Date date;

    @ExcelProperty("浮点数据")
    private Double doubleData;

    @ExcelProperty("整数")
    private Integer integerData;

    /**
     * Java String类型会丢失精度,建议定义为 BigDecimal|Double类型
     */
    @ExcelProperty("经度")
    private Double longitude;

    @ExcelProperty("纬度")
    private Double latitude;

}

1. Use anonymous listeners

    public static void manySheetRead() {
    
    
        String fileName = "D:\\TempFiles\\表格.xlsx";
        ExcelReader excelReader = EasyExcel.read(fileName).build();

        ReadSheet readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).headRowNumber(1)
                .registerReadListener(new AnalysisEventListener<DemoData>() {
    
     //注册读的监听器
                    /**
                     * 每解析一行excel数据,就会被调用一次
                     * @param demoData
                     * @param analysisContext
                     */
                    @Override
                    public void invoke(DemoData demoData, AnalysisContext analysisContext) {
    
    
                        log.info("readSheet1 解析到一条数据为:{}", JSON.toJSONString(demoData));
                    }

                    /**
                     * 全部解析完被调用
                     * @param analysisContext
                     */
                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    
    
                        log.info("readSheet1 全部解析完成");
                    }
                }).build();

        ReadSheet readSheet2 = EasyExcel.readSheet(1).head(DemoData2.class)
                .headRowNumber(2) // 注意Sheet2表头占了两行
                .registerReadListener(new AnalysisEventListener<DemoData2>() {
    
     //注册读的监听器
                    /**
                     * 每解析一行excel数据,就会被调用一次
                     * @param demoData2
                     * @param analysisContext
                     */
                    @Override
                    public void invoke(DemoData2 demoData2, AnalysisContext analysisContext) {
    
    
                        log.info("readSheet2 解析到一条数据为:{}", JSON.toJSONString(demoData2));
                    }

                    /**
                     * 全部解析完被调用
                     * @param analysisContext
                     */
                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    
    
                        log.info("readSheet2 全部解析完成");
                    }
                }).build();

        excelReader.read(readSheet1, readSheet2);
    }

insert image description here

2. Custom read listener

2.1 Custom Read Listener

Customize a general read listener and inherit the AnalysisEventListener class.

@Slf4j
public class CustomEasyExcelReadListener<T> extends AnalysisEventListener<T> {
    
    
    // 保存读取的对象
    private final List<T> rows = new ArrayList<>();

    // Sheet对应的名字
    private String sheetName = "";

    // 获取对应类
    private Class headClazz;

    // 此集合用来存储错误信息
    private final List<String> errorMessage = new ArrayList<>();

    public CustomEasyExcelReadListener(Class headClazz) {
    
    
        this.headClazz = headClazz;
    }


    /**
     * 通过Class获取类字段信息
     *
     * @param headClazz
     * @return
     * @throws NoSuchFieldException
     */
    public Map<Integer, String> getIndexNameMap(Class headClazz) throws NoSuchFieldException {
    
    
        Map<Integer, String> result = new HashMap<>();
        Field field;
        Field[] fields = headClazz.getDeclaredFields();     //获取类中所有的属性
        for (int i = 0; i < fields.length; i++) {
    
    
            field = headClazz.getDeclaredField(fields[i].getName());
            //log.info(String.valueOf(field));
            field.setAccessible(true);
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);//获取根据注解的方式获取ExcelProperty修饰的字段
            if (excelProperty != null) {
    
    
                int index = excelProperty.index();         //索引值
                String[] values = excelProperty.value();   //字段值
                StringBuilder value = new StringBuilder();
                for (String v : values) {
    
    
                    value.append(v);
                }
                result.put(index, value.toString());
            }
        }
        return result;
    }

    /**
     * 读取表头数据存在headMap中。如果你校验表头格式时可以使用。
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
    
    
        log.info("解析到一条表头数据:{}", JSON.toJSONString(headMap));
        Map<Integer, String> head = new HashMap<>();
        try {
    
    
            //通过Class获取到使用@ExcelProperty注解配置的字段
            head = getIndexNameMap(headClazz);
            log.info(String.valueOf(head));
        } catch (NoSuchFieldException e) {
    
    
            e.printStackTrace();
        }
        //解析到的excel表头和实体配置的进行比对
        Set<Integer> keySet = head.keySet();
        for (Integer key : keySet) {
    
    
            if (StringUtils.isEmpty(headMap.get(key))) {
    
    
                errorMessage.add("您上传的文件第" + (key + 1) + "列表头为空,请按照模板检查后重新上传");
            }
            if (!headMap.get(key).equals(head.get(key))) {
    
    
                errorMessage.add("您上传的文件第" + (key + 1) + "列表头与模板表头不一致,请检查后重新上传");
            }
        }
    }

    /**
     * 读取一行一行数据到object
     *
     * @param object
     * @param context
     */
    @Override
    public void invoke(T object, AnalysisContext context) {
    
    
        // 实际数据量比较大时,rows里的数据可以存到一定量之后进行批量处理(比如存到数据库),
        // 然后清空列表,以防止内存占用过多造成OOM
        rows.add(object);
    }

    /**
     * 在完成数据解析后进行的操作。AOP思想。
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
    
    
        // 当前sheet的名称 编码获取类似
        sheetName = context.readSheetHolder().getSheetName();
        log.info("sheetName = {} -> 所有数据解析完成, read {} rows", sheetName, rows.size());
    }

    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception 抛出异常
     * @param context   解析内容
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
    
    
        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        if (exception instanceof ExcelDataConvertException) {
    
    
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            errorMessage.add("第" + excelDataConvertException.getRowIndex() + "行,第" + (excelDataConvertException.getColumnIndex() + 1) +
                    "列数据类型解析异常,数据为:" + excelDataConvertException.getCellData());
            log.error("第{}行,第{}列数据类型解析异常,数据为:{}", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex() + 1,
                    excelDataConvertException.getCellData());
        }
    }

    public List<T> getRows() {
    
    
        return rows;
    }

    public Class getHeadClazz() {
    
    
        return headClazz;
    }

    public List<String> getErrorMessage() {
    
    
        return errorMessage;
    }

    public String getSheetName() {
    
    
        return sheetName;
    }
}

2.2 Example test

    /**
     * 自定义 ReadListener监听器测试方法
     */
    private static void manySheetReadWthCustomReadListener() {
    
    
        String fileName = "D:\\TempFiles\\表格.xlsx";
        ExcelReader excelReader = EasyExcel.read(fileName).build();

        CustomEasyExcelReadListener mySheet1Listener = new CustomEasyExcelReadListener(DemoData.class);
        CustomEasyExcelReadListener mySheet2Listener = new CustomEasyExcelReadListener(DemoData2.class);

        List<ReadSheet> readSheetList = new ArrayList<>();
        ReadSheet readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).headRowNumber(1)
                .registerReadListener(mySheet1Listener).build();

        ReadSheet readSheet2 = EasyExcel.readSheet(1).head(DemoData2.class)
                .headRowNumber(2) // 注意Sheet2表头占了两行
                .registerReadListener(mySheet2Listener).build();

        readSheetList.add(readSheet1);
        readSheetList.add(readSheet2);
        excelReader.read(readSheetList);

        System.out.println("============Sheet1 解析解析完成,数据如下================");
        //获取Sheet1监听器读到的数据,拿到的数据大家可以根据需求进行数据库操作
        List rows = mySheet1Listener.getRows();
        for (Object row : rows) {
    
    
            log.info("Sheet1 解析到一条数据为:{}", JSON.toJSONString(row));
        }
        //获取解决出的错误信息
        List<String> errorMessage = mySheet1Listener.getErrorMessage();
        log.info("Sheet1 解析错误信息为:{}", JSON.toJSONString(errorMessage));


        System.out.println("============Sheet2 解析解析完成,数据如下================");
        //获取Sheet2监听器读到的数据,拿到的数据大家可以根据需求进行数据库操作
        List<DemoData2> demoData2List = mySheet2Listener.getRows();
        for (DemoData2 demoData2 : demoData2List) {
    
    
            log.info("Sheet2 解析到一条数据为:{}", JSON.toJSONString(demoData2));
        }
        //获取解决出的错误信息
        List<String> errorMessage2 = mySheet2Listener.getErrorMessage();
        log.info("Sheet2 解析错误信息为:{}", JSON.toJSONString(errorMessage2));
    }

insert image description here

The invokeHeadMap method verification in the general custom read listener is a bit inappropriate. You can create targeted read listeners and handle them separately. The general invokeHeadMap method is used as needed.

For more operations, check the official document: https://easyexcel.opensource.alibaba.com/

– If you are hungry for knowledge, be humble if you are foolish.

Guess you like

Origin blog.csdn.net/qq_42402854/article/details/131382629