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);
相关推荐
苹果醋327 分钟前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
小蜗牛慢慢爬行28 分钟前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
azhou的代码园31 分钟前
基于JAVA+SpringBoot+Vue的制造装备物联及生产管理ERP系统
java·spring boot·制造
Swift社区1 小时前
Excel 列名称转换问题 Swift 解答
开发语言·excel·swift
wm10431 小时前
java web springboot
java·spring boot·后端
neeef_se2 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
℘团子এ2 小时前
js和html中,将Excel文件渲染在页面上
javascript·html·excel
路在脚下@9 小时前
spring boot的配置文件属性注入到类的静态属性
java·spring boot·sql
啦啦右一9 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien9 小时前
Spring Boot常用注解
java·spring boot·后端