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>,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优雅(如果那条路能走通的话)。

你可能感兴趣的:(笔记,java,学习)