JAVA- 使用注解实现 Excel 表头多语言导出
作者:JayJay
- 2022 年 5 月 16 日
本文字数:7785 字
阅读完需:约 26 分钟
前言
公司有个项目导出 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) && AnnoUtil.isExistAnno(field, TableName.class)) {
String[] fieldTableName = AnnoUtil.getAnnoValueByField(field, TableName.class);
if (!Objects.isNull(fieldTableName) && 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) && filedName.equals(valueStr) && 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) && filedName.equals(valueStr) && 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;
}
}
复制代码
执行结果
原来的表头
使用注解后
后语
这只是我想到的一种解决思路,过程中让我复习了 java 的反射机制和注解,这里只是提供一种思路,不一定需要我这种,可能别人的实现的方法比我的更高效更简洁。
划线
评论
复制
发布于: 刚刚阅读数: 3
JayJay
关注
还未添加个人签名 2022.03.10 加入
还未添加个人简介
评论