最近用easypoi做了个下载功能,感觉这个工具包还是挺方便的。
用户有个需求是,自己选择下载哪些列,选择下载的时候自己勾选列名。
阅读easypoi教程,2.4注解变种-更自由的导出,提供了ExcelExportEntity这种实现,相当于代码层面动态生成model。
感觉写起来还是很繁琐,没有model加@Excel注解这种一目了然。
所以想到动态修改Class文件,改变@Excel注解的属性值来显隐列。
主要的实现原理是:修改字节码文件,所以记得恢复
1、所有需要下载的列,按实体类模型设计加@Excel或@ExcelCollection或@ExcelEntity注解。
2、默认情况下,按照注解导出Excel。
3、若某一列不需要导出,如上面的name列,那么将@Excel注解的name属性修改为:主讲老师_ignore,但是主讲老师这个原始值需要记录下来。
4、用修改后的TeacherEntity导出Excel。
5、恢复@Excel注解的name属性为:主讲老师
上代码,实现如下:
Model是这个样子:
@ExcelTarget("teacherEntity")
public class TeacherEntity implements java.io.Serializable {
/** name */
@Excel(name = "主讲老师", needMerge=true)
private String name;
}
自定义工具类,提供如下通用方法:
1、修改注解的name值,并记录原值
/**
* 修改fields上@Excel注解的name属性,不需要下载的列,name修改增加_ignore.
* 保存原来的@Excel注解name属性值,本次生成后用来恢复
* @Params
* headers:用户勾选,由前端传来的列名,列名的key必须和Model字段对应
* clazz:model实体类
* excelMap:用来记录原值的map,因为用到了递归,这里返回值作为参数传入
* @return Map
*/
public static Map
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// @Excel注解
if (field.isAnnotationPresent(Excel.class)) {
boolean flag = true;
for (int i = 0; i < headers.size(); i++) {
JSONObject header = (JSONObject) headers.get(i);
if (field.getName().equals(header.get("key"))) {
flag = false;
break;
}
}
// 下载列不包括该字段,进行隐藏,并记录原始值
if (flag) {
Excel annotation = field.getAnnotation(Excel.class);
// 保存注解
excelMap.put(field.getName(), annotation.name());
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
changeAnnotationValue(handler, field.getName() + "_ignore");
}
// @ExcelCollection注解
} else if (field.isAnnotationPresent(ExcelCollection.class) && field.getType().isAssignableFrom(List.class)) {
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Class collectionClazz = (Class) pt.getActualTypeArguments()[0];
// 解决@ExcelCollection如果没有需要下载列的异常,java.lang.IllegalArgumentException: The 'to' col (15) must not be less than the 'from' col (16)
// 如果没有需要下载列,将@ExcelCollection忽略
Field[] collectionFields = collectionClazz.getDeclaredFields();
boolean flag = false;
out: for (Field temp : collectionFields) {
if (!temp.isAnnotationPresent(Excel.class)) {
continue;
}
for (int i = 0; i < headers.size(); i++) {
JSONObject header = (JSONObject) headers.get(i);
if (temp.getName().equals(header.get("key"))) {
flag = true;
break out;
}
}
}
if (flag) {
dynamicChangeAndSaveSourceAnnotation(headers, collectionClazz, excelMap);
} else {
ExcelCollection annotation = field.getAnnotation(ExcelCollection.class);
excelMap.put(field.getName(), annotation.name());
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
changeAnnotationValue(handler, field.getName() + "_ignore");
}
}
// @ExcelEntity注解
} else if (field.isAnnotationPresent(ExcelEntity.class)) {
Class entityClazz = field.getType();
dynamicChangeAndSaveSourceAnnotation(headers, entityClazz, excelMap);
}
}
return excelMap;
}
// 改变注解属性值,抽取的公共方法
private static void changeAnnotationValue(InvocationHandler handler, String propertyValue) {
try {
Field field = handler.getClass().getDeclaredField("memberValues");
field.setAccessible(true);
Map
memberValues.put("name", propertyValue);
} catch (Exception e) {
logger.error("替换注解属性值出错!", e);
}
}
2、用easypoi导出excel二进制文件
这里就不上代码了,可以参考easypoi教程http://easypoi.mydoc.io/#text_186900
3、生成excel后,恢复Model每个字段上@Excel注解name属性原值
/**
* 递归恢复@Excel原始的name属性
*/
public static void dynamicResetAnnotation(Class clazz, Map
if (excelMap.isEmpty()) {
return;
}
Field[] fields = clazz.getDeclaredFields();
try {
for (Field field : fields) {
if (field.isAnnotationPresent(Excel.class)) {
if (excelMap.containsKey(field.getName())) {
Excel annotation = field.getAnnotation(Excel.class);
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
String sourceName = excelMap.get(field.getName());
changeAnnotationValue(handler, sourceName);
}
} else if (field.isAnnotationPresent(ExcelCollection.class) && field.getType().isAssignableFrom(List.class)) {
// ExcelCollection修改过,才进行复原
if (excelMap.containsKey(field.getName())) {
ExcelCollection annotation = field.getAnnotation(ExcelCollection.class);
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
String sourceName = excelMap.get(field.getName());
changeAnnotationValue(handler, sourceName);
// ExcelCollection未修改过,递归复原泛型字段
} else {
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Class collectionClazz = (Class) pt.getActualTypeArguments()[0];
dynamicResetAnnotation(collectionClazz, excelMap);
}
}
} else if (field.isAnnotationPresent(ExcelEntity.class)) {
Class entityClazz = field.getType();
dynamicResetAnnotation(entityClazz, excelMap);
}
}
} catch (Exception e) {
logger.error("解析动态表头,恢复注解属性值出错!", e);
}
}
其他代码,包括easypoi生成excel、response写出文件,网上很多案例,就不多说了。
经测试,这个简单处理方法可行,@Excel、@ExcelCollection、@ExcelEntity均有考虑到,可以兼容Model嵌套的问题。
代码刚刚撸完,还没详细检查,可能有不完善的地方,大家发现,请留言指点!