IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> EasyExcel支持导入动态列 -> 正文阅读

[移动开发]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);
    }

结果
在这里插入图片描述

二、结束

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


  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:29:58  更:2022-05-05 11:30:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 0:08:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码