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);
相关推荐
薯条不要番茄酱9 分钟前
【SpringBoot】从零开始全面解析Spring MVC (一)
java·spring boot·后端
小林学习编程9 小时前
SpringBoot校园失物招领信息平台
java·spring boot·后端
愿你天黑有灯下雨有伞9 小时前
Spring Boot整合Kafka实战指南:从环境搭建到消息处理全解析
spring boot·kafka·linq
Clf丶忆笙10 小时前
SpringBoot异步处理@Async深度解析:从基础到高阶实战
spring boot
河山入梦来12 小时前
Excel表的导入与导出
excel
柯南二号12 小时前
【后端】SpringBoot用CORS解决无法跨域访问的问题
java·spring boot·后端
帮帮志13 小时前
vue实现与后台springboot传递数据【传值/取值 Axios 】
前端·vue.js·spring boot
杨不易呀15 小时前
Java面试高阶篇:Spring Boot+Quarkus+Redis高并发架构设计与性能优化实战
spring boot·redis·高并发·分布式锁·java面试·quarkus
Moshow郑锴16 小时前
Spring Boot 3 + Undertow 服务器优化配置
服务器·spring boot·后端
码农飞哥16 小时前
互联网大厂Java面试实战:Spring Boot到微服务的技术问答解析
java·数据库·spring boot·缓存·微服务·消息队列·面试技巧