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);
相关推荐
这孩子叫逆3 分钟前
Spring Boot项目的创建与使用
java·spring boot·后端
Jay_fearless36 分钟前
Redis SpringBoot项目学习
spring boot·redis
coderWangbuer1 小时前
基于springboot的高校招生系统(含源码+sql+视频导入教程+文档+PPT)
spring boot·后端·sql
Kenny.志1 小时前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端
sky丶Mamba2 小时前
Spring Boot中获取application.yml中属性的几种方式
java·spring boot·后端
千里码aicood3 小时前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
liuxin334455663 小时前
教育技术革新:SpringBoot在线教育系统开发
数据库·spring boot·后端
bin91536 小时前
【EXCEL数据处理】000017 案例 Match和Index函数。
excel
代码在改了7 小时前
springboot厨房达人美食分享平台(源码+文档+调试+答疑)
java·spring boot