Easy excel实战:读取文件自定义转化器不生效问题处理方案

    1.场景问题说明
    2.问题处理过程分析

1.场景问题说明

    现有一文件读取需求,需要将员工信息中的状态(正常或离职)进行翻译对应的数值,但是自定义转化器之后不生效。现将问题处理过程进行简单记录,希望能帮助到遇到同样问题的同学。
    待读取的原始文件:
Easy excel实战:读取文件自定义转化器不生效问题处理方案_第1张图片
    添加依赖:

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.8</version>
        </dependency>

    自定义格式转化器PersonalConvert:

public class PersonalConvert implements Converter<String> {

	// 指定转化参数的Java类型
    @Override
    public Class<?> supportJavaTypeKey() {
        return String.class;
    }

	// 指定转化参数对应的单元格类型
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

	// 单元格参数类型转化为Java类型处理逻辑
    @Override
    public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return "正常".equals(cellData.getStringValue())  ? "1":"2";
    }
}

    excel对应的实体类CustomPerson :

public class CustomPerson {

    private String name;
    private int age;

    @MobileValid
    private String mobile;

    @DateTimeFormat("yyyy-MM-dd")  // 官方提供的日期格式转化方式
    private String createTime;

    @ExcelProperty(value = "状态(1.正常;2.离职)"",converter = PersonalConvert.class)
    private String status;
	// 省略get/set
}	

    文件读取处理:

public static void main(String[] args) {

      
        ArrayList<CustomPerson> personArrayList = new ArrayList<CustomPerson>();
        
        EasyExcel.read("F:\\test.xls", CustomPerson.class, new PersonalListener<CustomPerson>(
                // 监听器中doAfterAllAnalysed执行此方法;所有读取完成之后处理逻辑
                personList->{
                    for (CustomPerson person:personList){
                        personArrayList.add(person);
                    }
                }
        )).sheet().doRead();  
        // 读取文件操作
        System.out.println(personArrayList);

    }

    输出内容:

2022-05-04 21:20:28.331 DEBUG [main] com.alibaba.excel.context.AnalysisContextImpl:96 - Began to readcom.alibaba.excel.read.metadata.holder.xls.XlsReadSheetHolder@74395b9a
[CustomPerson{name='张三', age=1, mobile='18523568956', createTime='2022-05-01', status=null}, CustomPerson{name='李四', age=2, mobile='12345678911', createTime='2022-05-02', status=null}, CustomPerson{name='王五', age=3, mobile='18523568956', createTime='2022-05-03', status=null}]

    发现状态参数并没有转化成对应的数值,自定义转化器不生效!如果想要实现参数值转化,也可以不借助excel自带的参数转化功能,在读取的CustomPerson集合信息后自己进行参数类型转化。当然easy excel支持参数类型转化就看一下不生效原因。

2.问题处理过程分析

    翻看源码发现读取字段转换主要实现逻辑在于:
ModelBuildEventListener.javabuildUserModel,读取每行数据并组装每行的结果模型。
主要看一下每行字段是如何进行解析的。

private Object buildUserModel(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,
        AnalysisContext context) {
 		// 省略部分代码
        Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap();
        BeanMap dataMap = BeanMapUtils.create(resultModel);
        // 获取所有的表头信息组合
        for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
            Integer index = entry.getKey();
            Head head = entry.getValue();
            String fieldName = head.getFieldName();
            if (!cellDataMap.containsKey(index)) {
                continue;
            }
            ReadCellData<?> cellData = cellDataMap.get(index);
            // 利用转化器进行数据格式转化
            Object value = ConverterUtils.convertToJavaObject(cellData, head.getField(),
                ClassUtils.declaredExcelContentProperty(dataMap, readSheetHolder.excelReadHeadProperty().getHeadClazz(),
                    fieldName), readSheetHolder.converterMap(), context, context.readRowHolder().getRowIndex(), index);
            if (value != null) {
                dataMap.put(fieldName, value);
            }
        }
        // 解析之后:CustomPerson{name='张三', age=1, mobile='18523568956', createTime='2022-05-01', status=null},status那列未做解析。
        return resultModel;
    }

debug调试Easy excel实战:读取文件自定义转化器不生效问题处理方案_第2张图片
    那就看headMap为什么只有四列,主要看ExcelHeadProperty.javainitOneColumnProperty

 private void initOneColumnProperty(int index, Field field, Boolean forceIndex) {
        ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
        List<String> tmpHeadList = new ArrayList<String>();
        String fieldName = FieldUtils.resolveCglibFieldName(field);
        boolean notForceName = excelProperty == null || excelProperty.value().length <= 0
            || (excelProperty.value().length == 1 && StringUtils.isEmpty((excelProperty.value())[0]));
        if (headMap.containsKey(index)) {
            tmpHeadList.addAll(headMap.get(index).getHeadNameList());
        } else {
            if (notForceName) {
                tmpHeadList.add(fieldName);
            } else {
                Collections.addAll(tmpHeadList, excelProperty.value());
            }
        }
        Head head = new Head(index, field, fieldName, tmpHeadList, forceIndex, !notForceName);
        headMap.put(index, head);
    }

DefaultAnalysisEventProcessorbuildHead

private void buildHead(AnalysisContext analysisContext, Map<Integer, ReadCellData<?>> cellDataMap) {
    
      // 省略部分代码
        for (Map.Entry<Integer, Head> entry : headMapData.entrySet()) {
            Head headData = entry.getValue();
            // 实体类中没有@ExcelProperty注解的成员变量或是有@ExcelProperty但value属性为空或不指定的成员变量都会添加到tmpHeadMap中,最终组装到headMap中。forceIndex和forceName如何进行设置值,参考下面debug截图
            if (headData.getForceIndex() || !headData.getForceName()) {
                tmpHeadMap.put(entry.getKey(), headData);
                continue;
            }
            List<String> headNameList = headData.getHeadNameList();
            String headName = headNameList.get(headNameList.size() - 1);
            // 实现的主要逻辑是对于标注@ExcelProperty的value属性,与excel表中的表头进行遍历匹配,如果@ExcelProperty中value属性与表头一致则添加到tmpHeadMap中,最终组装到headMap中
            for (Map.Entry<Integer, String> stringEntry : dataMap.entrySet()) {
                if (stringEntry == null) {
                    continue;
                }
                String headString = stringEntry.getValue();
                Integer stringKey = stringEntry.getKey();
                if (StringUtils.isEmpty(headString)) {
                    continue;
                }
                if (analysisContext.currentReadHolder().globalConfiguration().getAutoTrim()) {
                    headString = headString.trim();
                }
                // @ExcelProperty中value属性与表头一致则添加到tmpHeadMap中
                if (headName.equals(headString)) {
                    headData.setColumnIndex(stringKey);
                    tmpHeadMap.put(stringKey, headData);
                    break;
                }
            }
        }
        excelHeadPropertyData.setHeadMap(tmpHeadMap);
    }

    headData.getForceName()forceName属性获取过程源码分析:Easy excel实战:读取文件自定义转化器不生效问题处理方案_第3张图片
    继续往下看:
Easy excel实战:读取文件自定义转化器不生效问题处理方案_第4张图片
    从中可以得出结论:实体类属性标注ExcelProperty注解但是value不指定或为空或是value值必须和excel表头内容相同才可以支持类型转化。所以CustomPerson中将status修改为如下方式,测试发现都可以正常进行参数转化:
    指定value值与excel表头相同:

 @ExcelProperty(value = "状态",converter = PersonalConvert.class)
    private String status;

    不指定value值:

@ExcelProperty(converter = PersonalConvert.class)
    private String status;

    另外补充一种全局设置自定义转化器的方式(ExcelReaderSheetBuilder支持注册自定义转化器):

 public static void main(String[] args) {

       
        ArrayList<CustomPerson> personArrayList = new ArrayList<CustomPerson>();
       
        EasyExcel.read("F:\\test.xls", CustomPerson.class, new PersonalListener<CustomPerson>(
                // 监听器中doAfterAllAnalysed执行此方法;所有读取完成之后处理逻辑
                personList->{
                    for (CustomPerson person:personList){
                        personArrayList.add(person);
                    }
                }
        )).sheet().registerConverter(new PersonalConvert()).doRead();  
        // 读取文件操作
        System.out.println(personArrayList);

    }

    进行全局设置后进行文件读取操作,easy excel实体类中status就不用使用@ExcelProperty注解了。

你可能感兴趣的:(java,easy,excel)