EasyExcel之动态字段导出

EasyExcel虽然对excel导出做了很好的封装,但是对于动态表头导出做的却不是很好,今天接到一个需求,记录一下思路。

需要根据传入的字段,动态导出数据,且这个顺序是无序的,传入的字段范围是固定的。

当时乍一看这个需求,感觉还好,除了要排序麻烦点,其他都还好。粗想一下已经有了思路:

构造一个映射类,然后在写的时候利用includeColumnFiledNames这个API去限定导出的字段即可。但是这样有个问题,就是传入的字段顺序不是无序的,是根据你类里面构造的默认顺序来的。 这样动态调整顺序不能满足,那有其他办法么?我第一想法是在index这个注解上做文章,每次在去写excel的时候,对映射类字段的index去动态排序,这里用index而不是order是官方说的优先级是index>order,感觉应该可行,想到就写吧。

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IntrospectionException {
List<String> fieldList = new ArrayList<>();
        fieldList.add("girlFriend");
        fieldList.add("hobby");
        fieldList.add("name");

        for (int i = 0; i < headList.size(); i++) {
            ExcelProperty ano = Domain.class.getDeclaredField(fieldList.get(i)).getDeclaredAnnotation(ExcelProperty.class);
            InvocationHandler h = Proxy.getInvocationHandler(ano);
            Field f = h.getClass().getDeclaredField("memberValues");
            f.setAccessible(true);
            Map memberValues = (Map) f.get(h);
            memberValues.put("index", i);
        }
        
EasyExcel.write("test.xlsx",Domain.class)
                .sheet("test")
                .includeColumnFiledNames(fieldList)
                .doWrite(init());     
                
}

    private static List<Domain> init() {

        List<Domain> domainList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Domain domain = new Domain();
            domain.setName("li"+i);
            domain.setSex("男");
            domain.setAge(20 + i);
            domain.setHobby("看书");
            domain.setGirlFriend("小可爱");
            domainList.add(domain);
        }

        return domainList;
    }
                

按照上面这样写,跑了一次,好像能行,然后换个数据,在来一次之后,好像不生效了?于是debug看下,index的值是赋上了的,但是好像只有第一次生效,我当时第一反应,擦,这框架不会是用了缓存的吧,而且是不刷新的那种。然后一看源码,果然…

在ClassUtils里declaredFields方法,会创建一个FIELD_CACHE,每次字段映射其实用的就是这样一个Map。比较坑爹的是,这个Map构建的时候是用的computeIfAbsent(clazz, key -> {})这种写法,所以只要是同一个类,只会使用第一次构建的结构,后续重建的不会覆盖。

private static FieldCache declaredFields(Class<?> clazz) {
        if (clazz == null) {
            return null;
        }
        return FIELD_CACHE.computeIfAbsent(clazz, key -> {
            List<Field> tempFieldList = new ArrayList<>();
            Class<?> tempClass = clazz;
            // When the parent class is null, it indicates that the parent class (Object class) has reached the top
            // level.
            while (tempClass != null) {
                Collections.addAll(tempFieldList, tempClass.getDeclaredFields());
                // Get the parent class and give it to yourself
                tempClass = tempClass.getSuperclass();
            }
            // Screening of field
            Map<Integer, List<Field>> orderFieldMap = new TreeMap<Integer, List<Field>>();
            Map<Integer, Field> indexFieldMap = new TreeMap<Integer, Field>();
            Map<String, Field> ignoreMap = new HashMap<String, Field>(16);

            ExcelIgnoreUnannotated excelIgnoreUnannotated = clazz.getAnnotation(ExcelIgnoreUnannotated.class);
            for (Field field : tempFieldList) {
                declaredOneField(field, orderFieldMap, indexFieldMap, ignoreMap, excelIgnoreUnannotated);
            }
            return new FieldCache(buildSortedAllFieldMap(orderFieldMap, indexFieldMap), indexFieldMap, ignoreMap);
        });
    }

那如果还要走这条路的话,就只有每次去生成的时候,动态的生成一个新类,继承该类的所有属性,并设置index值。这个就可能要使用动态代理了,但是感觉真的走通有点耗精力,万一走不通还会得不偿失。

于是改方案二,第二种就是用很原始的办法去写了,header采用List<List<>>,data采用List<>,然后注意对应顺序,基本放弃官方的给的API了,完整代码如下:

 public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IntrospectionException {

        List<List<String>> headList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            List<String> head = new ArrayList<>();
            head.add("test" + i);
            headList.add(head);
        }
        
        
        List<String> fieldList = new ArrayList<>();
        fieldList.add("girlFriend");
        fieldList.add("hobby");
        fieldList.add("name");
        
        EasyExcel.write("test.xlsx",Domain.class)
                .head(headList)
                .sheet("test")
                //因为head不是字段,所以这个属性不生效
//                .includeColumnFiledNames(fieldList)


                .doWrite(initMyData(fieldList));
}

    private static List<List<Object>> initMyData(List<String> fieldList) throws NoSuchFieldException, IllegalAccessException {
        List<Domain> list = init();
        List<List<Object>> dataList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            for (int j = 0; j <fieldList.size() ; j++) {
                dataList.add(new ArrayList<>());
                Field f = null;
                try {
                    f = Domain.class.getDeclaredField(fieldList.get(j));
                    f.setAccessible(true);
                    dataList.get(i).add(f.get(list.get(i)));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return dataList;
    }

private static List<Domain> init() {

        List<Domain> domainList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Domain domain = new Domain();
            domain.setName("li"+i);
            domain.setSex("男");
            domain.setAge(20 + i);
            domain.setHobby("看书");
            domain.setGirlFriend("小可爱");
            domainList.add(domain);
        }

        return domainList;
    }

以上写法可以完美支持动态无序表格导出了,只是感觉代码没有动态设置index优雅(如果那条路能走通的话)。

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
EasyExcel是一个基于Java语言,功能强大的Excel操作工具,可以实现非常灵活的Excel导入导出功能。要实现自定义字段导出,可以通过实现EasyExcel的WriteHandler接口来实现。 首先,需要定义一个实现WriteHandler接口的类,例如: ```java public class CustomFieldHandler implements WriteHandler { // 自定义字段列表 private List<String> fields; public CustomFieldHandler(List<String> fields) { this.fields = fields; } @Override public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { // 在Sheet创建后,添加自定义字段行 Sheet sheet = writeSheetHolder.getSheet(); sheet.createRow(0); for (int i = 0; i < fields.size(); i++) { sheet.getRow(0).createCell(i).setCellValue(fields.get(i)); } } @Override public void beforeSheetFlush(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, Sheet sheet) { // 在Sheet刷新前,设置自定义字段列宽 for (int i = 0; i < fields.size(); i++) { sheet.setColumnWidth(i, 20 * 256); } } @Override public void afterRowCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, Row row, Integer rowIndex, Integer relativeRowIndex, Boolean isHead) { // 在每行创建后,设置自定义字段值 if (isHead) { return; } for (int i = 0; i < fields.size(); i++) { row.createCell(i).setCellValue("自定义字段值"); } } } ``` 上述代码中,CustomFieldHandler类实现了WriteHandler接口,并在其中实现了afterSheetCreate、beforeSheetFlush和afterRowCreate三个方法,分别对应Sheet创建后、Sheet刷新前和每行创建后的处理逻辑。其中,afterSheetCreate方法会在Sheet创建后添加一行自定义字段行,beforeSheetFlush方法会在Sheet刷新前设置自定义字段列宽,afterRowCreate方法会在每行创建后设置自定义字段值。 接下来,可以通过EasyExcel的WriteBuilder来构建导出流程,并将CustomFieldHandler类实例化并添加到WriteBuilder中。例如: ```java List<String> fields = new ArrayList<>(); fields.add("自定义字段1"); fields.add("自定义字段2"); // 构建导出流程 OutputStream out = new FileOutputStream("test.xlsx"); WriteWorkbook writeWorkbook = EasyExcel.write(out).registerWriteHandler(new CustomFieldHandler(fields)).build(); // 写入数据 WriteSheet writeSheet = EasyExcel.writerSheet().build(); writeWorkbook.write(dataList, writeSheet); // 完成导出 writeWorkbook.finish(); ``` 上述代码中,首先定义了一个自定义字段列表fields,然后通过EasyExcel的WriteBuilder构建导出流程,并将CustomFieldHandler类实例化并注册到WriteBuilder中。在写入数据时,会自动调用CustomFieldHandler中的方法来处理自定义字段导出逻辑。 通过上述方法,就可以很方便地实现自定义字段导出功能了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值