自定义注解实现Excel表头多语言导出

December 17, 2023
测试
测试
测试
测试
27 分钟阅读

前言

公司有个项目导出excel的时候,要求根据头部的语言编号参数来将导出的excel的表头输出指定语言的值,由于这个语言的值是动态的,所以不能使用固定的模板,因为是多公司的模式,每家公司的语言翻译可能也不一样,目前表头数据是存在数据库的,跟业务表名和业务表的字段名绑定,那要怎么实现多语言动态输出,我想到的是使用注解来实现这个功能。

本文的Excel导出框架使用的是alibaba的EasyExcel,可以去了解一下

实现思路

新建两个自定义注解,一个用于标注表名,一个用于字段名,因为表头的值是由EasyExcel提供的@ExcelProperty注解来写入的,所以我们利用反射的机制来判断类和属性上面的自定义注解动态修改@ExcelProperty注解的值来实现多语言输出

如何实现

自定义注解

新建@TableName注解,可以在类和属性使用,考虑到多表聚合的方式,值为String数组形式

@Target({ElementType.TYPE,ElementType.FIELD})
@Inherited
//使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
    String[] value() default {""};
}

新建@TableField注解,可以在属性使用,都为String类型,为了方便使用,拓展属性为tableName

@Target(ElementType.FIELD)
@Inherited
//使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {

    String value() default "";

    String tableName() default "";
}

实体类

@ColumnWidth(30)
@TableName("test_table")
public class ExcelDto implements Serializable {

    @ExcelProperty(value = "姓名", index = 0)
    @TableField("name")
    private String name;

    @ExcelProperty(value = "性别", index = 1)
    @TableName("test_table2")//也可以在类上的{"test_table","test_table2"}使用,或者age的注解方式
    @TableField("sex")
    private String sex;

    @ExcelProperty(value = "年龄", index = 2)
    @TableField(value = "age",tableName = "test_table2")
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

注解工具类

方便操作注解实现的工具类

public class AnnoUtil {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 获取class的所有属性
     *
     * @param objClass
     * @return
     */
    public static List<Field> getFidlds(Class<?> objClass) {
        List<Field> fields = new ArrayList<>();
        try {
            Field[] declaredFields = objClass.getDeclaredFields();
            if (declaredFields != null) {
                fields = Arrays.stream(declaredFields).collect(Collectors.toList());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fields;
    }

    /**
     * 判断class是否存在某个注解
     *
     * @param objClass  类class
     * @param annoClass 注解class
     * @return boolean true 存在,false 不存在
     */
    public static boolean isExistAnno(Class<?> objClass, Class<? extends Annotation> annoClass) {
        return objClass.isAnnotationPresent(annoClass);
    }

    /**
     * 判断属性(Field)是否存在某个注解
     *
     * @param field     类属性
     * @param annoClass 注解class
     * @return boolean true 存在,false 不存在
     */
    public static boolean isExistAnno(Field field, Class<? extends Annotation> annoClass) {
        return field.isAnnotationPresent(annoClass);
    }

    /**
     * 获取注解的值
     *
     * @param objClass  类class
     * @param annoClass 注解class
     * @return 返回T泛型类型
     */
    public static <T> T getAnnoValueByClass(Class<?> objClass, Class<? extends Annotation> annoClass) {
        return getAnnoValueByClass(objClass, annoClass, null);
    }


    /**
     * 获取类注解的值
     *
     * @param objClass      类class
     * @param annoClass     注解class
     * @param annoFieldName 注解属性名称
     * @return 返回T泛型类型
     */
    public static <T> T getAnnoValueByClass(Class<?> objClass, Class<? extends Annotation> annoClass, String annoFieldName) {
        Annotation annotation = objClass.getAnnotation(annoClass);
        T t = null;
        try {
            if (annotation != null) {
                InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
                Field field = invocationHandler.getClass().getDeclaredField("memberValues");
                field.setAccessible(true);
                Map<String, Object> memberValues = (Map<String, Object>) field.get(invocationHandler);
                t = (T) memberValues.get(Optional.ofNullable(annoFieldName).orElse("value"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return t;
    }


    /**
     * 设置类注解的值
     *
     * @param objClass  类class
     * @param annoClass 注解class
     * @param t         值
     */
    public static <T> void setAnnoVlaueByClass(Class<?> objClass, Class<? extends Annotation> annoClass, T t) {
        setAnnoVlaueByClass(objClass, annoClass, null, t);
    }

    /**
     * 设置类注解的值
     *
     * @param objClass      类class
     * @param annoClass     注解class
     * @param annoFieldName 注解属性名称
     * @param t             值
     */
    public static <T> void setAnnoVlaueByClass(Class<?> objClass, Class<? extends Annotation> annoClass, String annoFieldName, T t) {
        Annotation annotation = objClass.getAnnotation(annoClass);
        try {
            if (annotation != null) {
                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(Optional.ofNullable(annoFieldName).orElse("value"), t);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取字段注解的值
     *
     * @param field     字段对象
     * @param annoClass 注解class
     * @return 返回T泛型类型
     */
    public static <T> T getAnnoValueByField(Field field, Class<? extends Annotation> annoClass) {
        return getAnnoValueByField(field, annoClass, null);
    }

    /**
     * 获取字段注解的值
     *
     * @param field         字段对象
     * @param annoClass     注解class
     * @param annoFieldName 注解属性名称
     * @return 返回T泛型类型
     */
    public static <T> T getAnnoValueByField(Field field, Class<? extends Annotation> annoClass, String annoFieldName) {
        Annotation annotation = field.getAnnotation(annoClass);
        T t = null;
        try {
            if (annotation != null) {
                InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
                Field f = invocationHandler.getClass().getDeclaredField("memberValues");
                f.setAccessible(true);
                Map<String, Object> memberValues = (Map<String, Object>) f.get(invocationHandler);
                t = (T) memberValues.get(Optional.ofNullable(annoFieldName).orElse("value"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return t;
    }


    /**
     * 设置字段注解的值
     *
     * @param field     字段对象
     * @param annoClass 注解class
     * @param t         值
     */
    public static <T> void setAnnoValueByField(Field field, Class<? extends Annotation> annoClass, T t) {
        setAnnoValueByField(field, annoClass, null, t);
    }

    /**
     * 设置字段注解的值
     *
     * @param field         字段对象
     * @param annoClass     注解class
     * @param annoFieldName 注解属性名称
     * @param t             值
     */
    public static <T> void setAnnoValueByField(Field field, Class<? extends Annotation> annoClass, String annoFieldName, T t) {
        Annotation annotation = field.getAnnotation(annoClass);
        try {
            if (annotation != null) {
                InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
                Field f = invocationHandler.getClass().getDeclaredField("memberValues");
                f.setAccessible(true);
                Map<String, Object> memberValues = (Map<String, Object>) f.get(invocationHandler);
                memberValues.put(Optional.ofNullable(annoFieldName).orElse("value"), t);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

核心代码实现

首先传入需要反射的实体类class,然后获取类的注解,判断@TableName@TableField@ExcelProperty,获取注解的值,根据注解的组合来判断来输出多语言,找不到对应的字段和默认是去@ExcelProperty自身的值输出,注解之间可以灵活配置,具体用法已经在实体类给出

public class HeaderUtil {

    /**
     * 设置头部国际化,注解
     * @param classes 需要反射的实体类
     * @param languageCode 语言编码
     */
    public static void setExcelHead(Class<?>[] classes,String languageCode) {
        //模拟数据库获取数据
        Map<String, List<FieldModel>> tableFieldMap = getTableIl18nData();
        Class<?>[] entityClassArray = classes;
        if (entityClassArray == null || entityClassArray.length <= 0) {
            return;
        }
        for (Class<?> entityClass : entityClassArray) {
            List<Field> fidlds = AnnoUtil.getFidlds(entityClass);
            if (CollectionUtils.isEmpty(fidlds)) {
                return;
            }
            String[] classTableName = AnnoUtil.getAnnoValueByClass(entityClass, TableName.class);
            fidlds.forEach(field -> {
                //@TableField和@ExcelProperty 其中一个任意注解不存在,就不执行
                if (!AnnoUtil.isExistAnno(field, TableField.class) || !AnnoUtil.isExistAnno(field, ExcelProperty.class)) {
                    return;
                }
                String filedName = AnnoUtil.getAnnoValueByField(field, TableField.class);
                String tableName = AnnoUtil.getAnnoValueByField(field, TableField.class, "tableName");
                if (StringUtils.isBlank(tableName) &amp;&amp; AnnoUtil.isExistAnno(field, TableName.class)) {
                    String[] fieldTableName = AnnoUtil.getAnnoValueByField(field, TableName.class);
                    if (!Objects.isNull(fieldTableName) &amp;&amp; fieldTableName.length > 0) {
                        tableName = fieldTableName[0];
                    }
                }
                //如果属性Field上有tableName,以这个为准,否则以类的tableName为准
                if (StringUtils.isBlank(tableName)) {
                    if (Objects.isNull(classTableName) || classTableName.length <= 0) {
                        return;
                    }
                    for (String cTName : classTableName) {
                        List<FieldModel> tableFields = tableFieldMap.get(cTName);
                        tableFields.forEach(item -> {
                            String valueStr = item.getFieldName();
                            JSONObject value = item.getJsonObject();
                            boolean isInternational = !(Objects.isNull(value) || value.isEmpty());
                            if (!Objects.isNull(value) &amp;&amp; filedName.equals(valueStr) &amp;&amp; isInternational) {
                                //只修改头的最后一个的值,前面一般作为合并单元格,目前不需要
                                String[] headStr = AnnoUtil.getAnnoValueByField(field, ExcelProperty.class);
                                headStr[headStr.length - 1] = (String) value.get(languageCode);
                                AnnoUtil.setAnnoValueByField(field, ExcelProperty.class, headStr);
                            }
                        });
                    }
                } else {
                    List<FieldModel> tableFields = tableFieldMap.get(tableName);
                    tableFields.forEach(item -> {
                        String valueStr = item.getFieldName();
                        JSONObject value = item.getJsonObject();
                        boolean isInternational = !(Objects.isNull(value) || value.isEmpty());
                        if (!Objects.isNull(value) &amp;&amp; filedName.equals(valueStr) &amp;&amp; isInternational) {
                            String[] headStr = AnnoUtil.getAnnoValueByField(field, ExcelProperty.class);
                            headStr[headStr.length - 1] = (String) value.get(languageCode);
                            AnnoUtil.setAnnoValueByField(field, ExcelProperty.class, headStr);
                        }
                    });
                }
            });
        }
    }

    /**
     *  模拟数据,真实数据可根据实际清空获取
     * @return
     */
    public static Map<String,List<FieldModel>> getTableIl18nData(){
        Map<String,List<FieldModel>> data = new HashMap<>();
        List<FieldModel> fieldModels = new ArrayList<>();

        FieldModel name = new FieldModel();
        name.setFieldName("name");
        name.setJsonObject(getIl18n("name","姓名"));
        fieldModels.add(name);

        FieldModel sex = new FieldModel();
        sex.setFieldName("sex");
        sex.setJsonObject(getIl18n("sex","性别"));
        fieldModels.add(sex);

        FieldModel age = new FieldModel();
        age.setFieldName("age");
        age.setJsonObject(getIl18n("age","年龄"));
        fieldModels.add(age);

        data.put("test_table",fieldModels);

        List<FieldModel> fieldModels2 = new ArrayList<>();

        FieldModel sex2 = new FieldModel();
        sex2.setFieldName("sex");
        sex2.setJsonObject(getIl18n("sex2","性别2"));
        fieldModels2.add(sex2);

        FieldModel age2 = new FieldModel();
        age2.setFieldName("age");
        age2.setJsonObject(getIl18n("age2","年龄2"));
        fieldModels2.add(age2);

        data.put("test_table2",fieldModels2);
        return data;
    }

    /**
     * 构建多语言json 返回
     * @param en 英语
     * @param cn 中文
     * @return
     */
    public static JSONObject getIl18n(String en,String cn){
        JSONObject jsonObject = new JSONObject();
        jsonObject.set("en",en);
        jsonObject.set("cn",cn);
        return jsonObject;
    }
}

测试方法

public class ExportMain {

    @Test
    public void simpleWrite() {
        String fileName = "d:/test/export_demo" + System.currentTimeMillis() + ".xlsx";
        Class<?>[] classes = {ExcelDto.class};
        HeaderUtil.setExcelHead(classes,"en");
        EasyExcel.write(fileName, ExcelDto.class)
                .sheet("模板")
                .doWrite(() -> {
                    // 模拟数据
                    return data();
                });
    }


    /**
     * 模拟数据,正常应该是从数据库获取
     *
     * @return
     */
    public static List<ExcelDto> data() {
        List<ExcelDto> data = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            ExcelDto excelDto = new ExcelDto();
            excelDto.setName(RandomUtil.randomString(5));
            excelDto.setSex(RandomUtil.randomInt(0, 1) == 1 ? "男" : "女");
            excelDto.setAge(RandomUtil.randomInt(18, 40));
            data.add(excelDto);
        }
        return data;
    }
}

执行结果

image.png
image.png

后语

这只是我想到的一种解决思路,过程中让我复习了java的反射机制和注解,这里只是提供一种思路,不一定需要我这种,,可能别人的实现的方法比我的更高效更简洁。

继续阅读

更多来自我们博客的帖子

如何安装 BuddyPress
由 测试 December 17, 2023
经过差不多一年的开发,BuddyPress 这个基于 WordPress Mu 的 SNS 插件正式版终于发布了。BuddyPress...
阅读更多
Filter如何工作
由 测试 December 17, 2023
在 web.xml...
阅读更多
如何理解CGAffineTransform
由 测试 December 17, 2023
CGAffineTransform A structure for holding an affine transformation matrix. ...
阅读更多