EasyExcel支持导入动态列


前言

我们在使用EasyExcel做导入功能时,通过在实体或者VO加@ExcelProperty(“测试”)注解来实现列的一一对应,read()之后获取一个List<T>的结果集,得到java的数据类型然后在进行业务操作
但EasyExcel这种通过注解的方式实现导入就必须使用事先定义好数据类型来接收,它不支持对象中的子对象,也不能导入excel带有动态列的数据(即相同列名下的多个数据列)
换做java的数据结构即如下结构:
在这里插入图片描述
这里使用原生的EasyExcel并不能接收到多个列名为 测试1 的数据
这里提供一种解决方式,它能够实现动态列的解析,实例化对象中的子对象这种数据结构(仅支持二级的嵌套,即一对多的这种关系)


一、代码

/* 注解用来支持解析构建子对象 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelDynamic {
}

/* 解析导入数据,存储解析到的动态列数据(其中用到一些工具类大家可以自己实现) */
public class DynamicReadListener extends AnalysisEventListener<Map<Integer, ReadCellData<?>>> {

    /**
     * log
     */
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 表头数据(存储所有的表头数据)
     */
    private final List<Map<Integer, String>> headList = new ArrayList<>();

    /**
     * 数据体
     */
    private final List<Map<Integer, String>> dataList = new ArrayList<>();

    /**
     * 动态列 索引
     */
    private final List<List<Integer>> dynamicIndex = new ArrayList<>();

    /**
     * 动态列数据
     */
    private final List<Map<String, List<Object>>> dynamicColumns = new ArrayList<>();

    /**
     * 实例化 Listener
     * @return Listener
     */
    public static DynamicReadListener init(){
        return new DynamicReadListener();
    }

    /**
     * 这里会一行行的返回头
     * @param headMap 表头
     * @param context 上线文
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("解析到表头:{}", JsonUtil.toJson(headMap));
        //保存表头
        headList.add(headMap);
        //保存动态列索引
        //CollectUtil.repeatNode(headMap) 获取map中重复value的keys
        dynamicIndex.add(CollectUtil.repeatNode(headMap));
    }


    @Override
    public void invoke(Map<Integer, ReadCellData<?>> data, AnalysisContext context) {
        /*
         * 保存数据行
         * 这里因为接收类型是 ReadCellData 就未处理
         * ReadCellData 有getStringValue 可供获取cell值
         */
        //dataList.add(data);
        ReadSheetHolder holder = context.readSheetHolder();
        try {
            handleDynamicColumns(data, holder.getHeadRowNumber() - 1, holder.getRowIndex() - 1, headList.size());
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 后置处理
     * @param context 上线文
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {}

    /**
     * 根据当前数据行,获取动态列数据
     *
     * @param data 当前数据
     * @param dataRowIndex 数据行索引
     * @param headRowIndex 数据头索引
     * @param columnSize 列长度,初始化map需要
     */
    private void handleDynamicColumns(Map<Integer, ReadCellData<?>> data, Integer headRowIndex, Integer dataRowIndex, Integer columnSize) throws NoSuchFieldException, IllegalAccessException {
        if (!dynamicIndex.isEmpty()){
            Map<String, List<Object>> columns = new HashMap<>(columnSize);
            Map<Integer, String> targetHead = headList.get(headRowIndex);
            for (Integer targetIndex : dynamicIndex.get(headRowIndex)) {
                String head = targetHead.get(targetIndex);
                String value = data.get(targetIndex).getStringValue();
                if (Objects.isNull(columns.get(head))){
                    //不要使用Arrays.asList 因为返回的list 没有重写add方法
                    columns.put(head, ListUtil.asList(value));
                }else {
                    columns.get(head).add(value);
                }
            }
            dynamicColumns.add(columns);
        }
    }

    public List<Map<Integer, String>> getHeadList() {
        return headList;
    }

    public List<Map<Integer, String>> getDataList() {
        return dataList;
    }

    public List<Map<String, List<Object>>> getDynamicColumns() {
        return dynamicColumns;
    }
}
/* easyExcel工具类 */
public class Excels extends EasyExcel{

    private Excels() {}

    /**
     * 导入
     * @param fileStream 文件流
     * @param clazz 导入类型class
     * @param <T> 导入类型
     * @return List<T>
     */
    public static <T> List<T> imports(InputStream fileStream, Class<T> clazz){
        return read(fileStream).head(clazz).sheet().doReadSync();
    }

    /**
     * 导入带有动态列的数据
     *
     * @param fileStream 文件流
     * @param clazz 导入类型class
     * @param <T> 导入类型
     * @return List<T>
     */
    public static <T> List<T> importsDynamic(InputStream fileStream, Class<T> clazz) throws IllegalAccessException, InstantiationException {
        //初始化 处理动态列 readListener
        DynamicReadListener dynamicReadListener = DynamicReadListener.init();
        //初始化 同步读取数据 readListener
        SyncReadListener syncReadListener = new SyncReadListener();
        /*
         * useDefaultListener = false
         * 默认的 readListener 即 ModelBuildEventListener 会第一个去处理数据,导致ReadHolder中的 currentRowAnalysisResult 已转为 Model 类型,
         * dynamicReadListener 调用到 invoke 时会报错, 因此需要 dynamicReadListener 排在 readListenerList 的第一位,保证能够接收到 map 类型,处理动态列
         * 但我们仍需要 ModelBuildEventListener 所以我们手动注册
         */
        ExcelReaderSheetBuilder sheet = read(fileStream).useDefaultListener(false).head(clazz).sheet();
        sheet.registerReadListener(dynamicReadListener);
        //注册 map转 model readListener
        sheet.registerReadListener(new ModelBuildEventListener());
        sheet.registerReadListener(syncReadListener);
        sheet.doRead();
        return build((List<T>) syncReadListener.getList(), dynamicReadListener);
    }

    /**
     * 处理源数据 为其实例化其中的动态列属性
     *
     * @param targets 处理目标List<T> 中的动态列 为其实例化动态列属性
     * @param <T> 处理数据类型
     * @return 源数据
     * @throws IllegalAccessException IllegalAccessException
     */
    private static <T> List<T> build(List<T> targets, DynamicReadListener listener) throws IllegalAccessException, InstantiationException {
        if (CollectionUtils.isNotEmpty(targets)){
            for (int i = 0; i < targets.size(); i++) {
                T target = targets.get(i);
                // 初始化带有 @ExcelDynamic 标志的属性
                for (Field targetField : target.getClass().getDeclaredFields()) {
                    if (Objects.nonNull(targetField.getAnnotation(ExcelDynamic.class))){
                        targetField.setAccessible(true);
                        targetField.set(target, build(listener.getDynamicColumns().get(i), targetField.getType()));
                    }
                }
            }
        }
        return targets;
    }

    /**
     * 根据class、当前行动态列数据 构建动态列属性
     *
     * @param dynamicRow 当前行动态列数据
     * @param clazz 处理类型
     * @param <T> 处理类型
     * @return 多列转List
     */
    private static <T> T build(Map<String, List<Object>> dynamicRow, Class<T> clazz) throws InstantiationException, IllegalAccessException {
        if (Objects.isNull(clazz)){
            throw new NullPointerException("class not support null value");
        }
        //处理类型属性list
        Field[] fields = clazz.getDeclaredFields();
        //待处理命中动态列数据
        Map<Field, List<Object>> hitColumn = new HashMap<>(fields.length);
        //开始筛选命中动态列
        dynamicRow.forEach((head, columns)->{
            //遍历 <T> 属性
            for (Field field : fields) {
                //获取属性注解
                ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
                if (Objects.nonNull(annotation)){
                    //根据注解值匹配
                    Object[] annotationKeys = annotation.value();
                    for (Object annotationKey : annotationKeys) {
                        if (!Objects.equals("", annotationKey) && Objects.equals(head, annotationKey)){
                            if (CollectionUtils.isNotEmpty(columns)){
                                hitColumn.put(field, columns);
                                break;
                            }
                        }
                    }
                }
            }
        });
        return handle(hitColumn, clazz);
    }

    /**
     * 处理命中的动态列
     * @param hitColumn 待处理数据
     * @param clazz 类型
     * @param <T> T
     * @return T
     */
    private static <T> T handle(Map<Field, List<Object>> hitColumn, Class<T> clazz) throws InstantiationException, IllegalAccessException {
        //处理返回结果
        if(CollectionUtils.isNotEmpty(hitColumn.keySet())){
            T target = clazz.newInstance();
                hitColumn.forEach((field, columns) -> {
                    try {
                        List<Object> targetFieldList = new ArrayList<>();
                        for (Object column : columns) {
                            try {
                                Object targetValue = Objects.requireNonNull(getGenericClass(field)).getConstructor(String.class).newInstance((String)column);
                                targetFieldList.add(targetValue);
                            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                                e.printStackTrace();
                            }
                        }
                        field.setAccessible(true);
                        field.set(target, targetFieldList);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                });
            return target;
        }
        return null;
    }

    /**
     * 通过 Field 获取其泛型
     * @param field 类属性
     * @return 泛型类
     */
    private static Class<?> getGenericClass(Field field){
        Type genericType = field.getGenericType();
        if (genericType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            // 获取成员变量的泛型类型信息
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                return (Class<?>) actualTypeArgument;
            }
        }
        return null;
    }
}

二、使用

文件数据
在这里插入图片描述

测试

	/* 测试 */
    @Test
    public void test1() throws FileNotFoundException, IllegalAccessException, InstantiationException {
        List<A> as = Excels.importsDynamic(new FileInputStream(new File("C:\\Users\\caobinghui\\Desktop\\test.xlsx")), A.class);
        as.forEach(System.out::println);
    }

结果
在这里插入图片描述

二、结束

代码拙劣,大家可以自己去试着优化
觉得有帮助的可以点个赞,谢谢!!


  • 12
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
easyexcel可以实现导入动态的功能。你可以使用以下步骤来实现: 1. 引入easyexcel的maven依赖,确保在你的项目中有以下依赖: ``` <!-- easyexcel --> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.2.1</version> </dependency> ``` 2. 准备一个模板文件,比如template1.xlsx,模板文件中的可以根据需求动态生成。 3. 编写代码实现导入动态的功能。你可以使用以下代码作为参考: ``` @Test public void test1() throws FileNotFoundException, IllegalAccessException, InstantiationException { // 读取Excel文件并导入动态 List<A> as = Excels.importsDynamic(new FileInputStream(new File("C:\\Users\\caobinghui\\Desktop\\test.xlsx")), A.class); // 打印导入的数据 as.forEach(System.out::println); } ``` 这样,你就可以使用easyexcel导入动态了。请确保你的模板文件中的导入对象的属性匹配。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [EasyExcel支持导入动态](https://blog.csdn.net/weixin_44204191/article/details/124561932)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [easyexcel导入导出+动态+自定义样式](https://blog.csdn.net/qq_34783476/article/details/130364172)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值