EasyExcel之动态表头导出不生效

今天接到一个优化需求,表格导出后的表头顺序和页面不一致,要优化成一致的。根据传入的字段,动态导出数据,并保证顺序。

我看到导出的实体类都有@ExcelProperty注解,同时也在官网查看了这注解的含义和使用。

@ExcelProperty有两个属性可以帮我们排序:index和order,所以我就想每次在去写excel的时候,对映射类字段的index去动态排序。
注意index的使用

  • index的值相同时会抛出异常
  • index的值不连续时会插入空白列

然后想直接用反射动态修改index

java 复制代码
    /**
     *
     * @param headList 前端上送的动态表头
     * @param clazz 导出实体类的class对象
     */
    private static void handleExcelHead(List<String> headList, Class<?> clazz) {
        try {
            for (String excelHead : headList) {
                Field declaredField = clazz.getDeclaredField(excelHead);
                ExcelProperty annotation = declaredField.getAnnotation(ExcelProperty.class);
                InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
                Field field = invocationHandler.getClass().getDeclaredField("memberValues");
                field.setAccessible(true);
                Map<String, Object> memberValues = (Map<String, Object>) field.get(invocationHandler);
                memberValues.put("index", headList.indexOf(excelHead));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

完整代码如下:

java 复制代码
public class Test {
    public static void main(String[] args) throws Exception {
        List<String> headList = new ArrayList<>();
        headList.add("name");
        headList.add("sex");
        headList.add("age");
        // 动态处理表头
        handleExcelHead(headList,User.class);

        EasyExcel.write("test.xlsx").head(User.class)
                .sheet("test")
                .includeColumnFiledNames(headList)
                .doWrite(init());
    }
    /**
     *
     * @param headList 前端上送的动态表头
     * @param clazz 导出实体类的class对象
     */
    private static void handleExcelHead(List<String> headList, Class<?> clazz) {
        try {
            for (String excelHead : headList) {
                Field declaredField = clazz.getDeclaredField(excelHead);
                ExcelProperty annotation = declaredField.getAnnotation(ExcelProperty.class);
                InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
                Field field = invocationHandler.getClass().getDeclaredField("memberValues");
                field.setAccessible(true);
                Map<String, Object> memberValues = (Map<String, Object>) field.get(invocationHandler);
                memberValues.put("index", headList.indexOf(excelHead));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private static List<User> init() {

        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setName("test"+i);
            user.setSex("男");
            user.setAge(20 + i);
            userList.add(user);
        }

        return userList;
    }
}

代码运行后发现和预期结果一样。但是当我则调整表头顺序并增加表头后发现不生效了。后来debug发现由于使用的是class对象,只要这个jvm不重启或者这个对象不被回收修改的index就一直存在,所以我们每次应该把index恢复成默认值。但是发现还是不行。后来在想这框架应该是使用了缓存吧,毕竟导出实体是固定的。果然:

ClassUtils 这里使用了缓存

java 复制代码
    public static final Map<Class<?>, FieldCache> FIELD_CACHE = new ConcurrentHashMap<>();


    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);
        });
    }

这下难住我了,这个缓存也不刷新。后来想他把缓存放在map里,key是Class,我直接每次都给他remove不就可以了。加了一行代码放在处理表头前边,果然可行!

java 复制代码
ClassUtils.FIELD_CACHE.remove(User.class);
相关推荐
java干货34 分钟前
深度解析:Spring Boot 配置加载顺序、优先级与 bootstrap 上下文
前端·spring boot·bootstrap
沉到海底去吧Go1 小时前
【工具教程】PDF电子发票提取明细导出Excel表格,OFD电子发票行程单提取保存表格,具体操作流程
pdf·excel
sclibingqing2 小时前
SpringBoot项目接口集中测试方法及实现
java·spring boot·后端
开开心心就好3 小时前
高效Excel合并拆分软件
开发语言·javascript·c#·ocr·排序算法·excel·最小二乘法
KK溜了溜了4 小时前
JAVA-springboot log日志
java·spring boot·logback
我命由我123455 小时前
Spring Boot 项目集成 Redis 问题:RedisTemplate 多余空格问题
java·开发语言·spring boot·redis·后端·java-ee·intellij-idea
面朝大海,春不暖,花不开5 小时前
Spring Boot消息系统开发指南
java·spring boot·后端
hshpy5 小时前
setting up Activiti BPMN Workflow Engine with Spring Boot
数据库·spring boot·后端
jay神5 小时前
基于Springboot的宠物领养系统
java·spring boot·后端·宠物·软件设计与开发
不知几秋6 小时前
Spring Boot
java·前端·spring boot