今天接到一个优化需求,表格导出后的表头顺序和页面不一致,要优化成一致的。根据传入的字段,动态导出数据,并保证顺序。
我看到导出的实体类都有@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);