前言
之前写的项目中,有个需求,需要导出导入Excel表格;
本来很简单的一件事,等到具体实现的时候,才发现,楼上部门给的表格很乱;
比如 Sheet A
单元簿中,公司名称在B
列,那么 Sheet B
单元簿中,公司列就可能已经在 C
列上,或者直接没有公司列了 ;
或则,有的单元格有数据,有的没有数据,或者日期单元格中写的不是日期,是个字符串;
或者,同一个excel
表的数据,来自多个 po
类对象,比如 A
列数据最后要封装到到 公司类
中,B
列数据最后封装到 合同类
里面 。这是最变态的;
总而言之,Excel
乱的很,根本没有模板,因为这些Excel
数据在写程序之前,已经存在了 ;
而给我的任务,则是导入这些表格,并且帮他们保存到数据库中,还要保持数据之间的关系;
现在摆在面前的是:别人是Excel
适应程序,我面前的则是写程序兼容Excel
;
思来想去,写一个通用的工具类吧,也方便以后再遇到类似的问题,顺便展示下我实习生的能力哈;
当前支持的功能
将
excel
导入到内存中,放在一个Map<String,List<Object>>
里面;K
是类的全限定名,V
是保存数据的list
集合 ;在读取数据的时候,可以对数据进行校验,支持正则表达式;
通过在
po
类的字段上配置注解,进行数据的检验,不配置,则默认匹配任何数据;如果数据校验结果有错误,则返回返回一个
list
集合,里面封装错误信息,便于前台显示;错误信息,也是用过注解,自己定义错误信息,默认错误信息为空串 ;
方法api
获取检验信息
/**
* 获取保存错误信息的集合
* @return list
*/
public List<String> getIndexErrors();
返回一个保存哪行哪列发生错误的信息的集合;如果没有错误信息,则集合的大小为
0
;导入excel表格
/**
* 功能:导入 excel表格,将内容保存到对应的对象的集合中 ;
*
* @param file 需要导入的 excel 表格
* @param classes excel 中 数据最后封装到哪些对象中;
*/
public Map<String, List<Object>> importExcel(File file, List<Class> classes)
throws IOException,
InvalidFormatException,
IllegalAccessException,
InstantiationException
File
参数是要到导入的excel
表格对象List<Class>
参数是excel
表格数据来自那些类,里面保持它们的Class
对象 ;
配置
要想工具正确的工作,还需要做一些配置的;
配置效果如下:
@FromWho(className = "cn.hyc.vo.ContractVo")
public class Contract {
@ExcelVOAttribute(name = "合同ID", column = "A", CLASS = Integer.class)
private Integer contId;
@ExcelVOAttribute(name = "项目名字", column = "B")
private String itemName;
....
}
@FromWho
只有一个
String className() ;
属性,该属性使用在最基类上(Object除外
),内容为最子类的全限定名;比如有3个类 ,A继承B,B继承C;则该注解写在C类上,里面内容为 A ;
@ExcelVOAttribute
/**
* 导出到Excel中的列的名字.也是能读取识别的重要因子
*/
String name(); /**
* 配置列的名称,对应A,B,C,D,导出的时候,想让数据在哪一列,就写对应的列名字
*/
String column(); /**
* 该列是什么类型,指定列的数据类型,默认是String 类型
*/
Class CLASS() default String.class ; /**
* 使用正则表达式 对数据的内容进行校验。默认匹配任何数据
*/
String regex() default "(.|\n)*"; /**
* 错误提示信息,默认为空串
*/
String info() default "";
如何使用(Demo)
导入
方法参数添加的类是最子类;
List<Class> classes = new ArrayList<>();
// 将最终结果封装到哪一个类里面,这里就添加谁,可添加多个
classes.add(Class.forName("cn.hyc.vo.ContractVo"));
MyExcelUtils myExcelUtils = new MyExcelUtils();
// 返回excel的数据,封装到各自对应的po对象里面
Map<String, List<Object>> map = myExcelUtils.importExcel(new File("xxx.xlms"), classes);获取错误信息
List<String> errors = myExcelUtils.getIndexErrors();
if (errors.size() > 0) {
sonObject.put("result", "0");
jsonObject.put("resultInfo", JSONObject.toJSON(errors));
return jsonObject.toJSONString();
}从Map里面获取自己想要的po类数据‘
List listObject = map.get("cn.hyc.vo.ContractVo");
if (listObject.size() == 0) {
continue;
}
// 根据类的全限定名获取
List<ContractVo> list = new ArrayList<>(listObject.size());
// 进行强转
for (int j = 0; j < listObject.size(); j++) {
list.add((ContractVo) listObject.get(j));
}
实现思路(该工具类可正确的一个大前提)
无论你多个单元簿,里面的列怎么变换,怎么增删,但是你那个列的名字是不变的;(这是大前提,如果没有这个前提,则本工具将只读取和配置 name
的属性一样的列)
比如,公司名称数据那列,无论你一会放在A
列,一会放在B
列,但是你的那一列名字总归是 公司名称
;
从这个入手;
比如现在有5列,分类来自Aaa Bbb Ccc
三个类。
Aaa | A — name | B — age |
Bbb | D — time | E — money |
Ccc | E — nickname |
按照上面的分配;去配置Aaa Bbb Ccc
三个类 ;
仅演示配置 Aaa
@FromWho(className = "Aaa")
public class Aaa{
@ExcelVOAttribute(name = "姓名", column = "A", CLASS = Integer.class)
private String name;
@ExcelVOAttribute(name = "年龄", column = "B")
private String age;
....
}
工具内部的实现原理就是:先加载那些,传进来的Class
对象,通过反射,获取其所有的子段,包括父类的字段,直到最基类;
拿到字段以后,只获取那些标注了特定注解的字段;
然后,获取注解中的内容:
比如加载 Aaa
先获取其头上标注的 @FromWho
的内容,知道这个类的字段上面的列名字都是来自 Aaa
,然后读取 Aaa
的字段,获取使用了 @ExcelVOAttribute
的内容,知道 在 excel
中 姓名
列的内容最后封装到 Aaa
的 name
字段上 ,知道 在 excel
中 年龄
列的内容最后封装到 Aaa
的 age
字段上 ;以此类推
关系绑定代码如下:
/**
* 将配置中配置的类的有注解字段,加载进 map 里面,K-V K是字段 V是注解的值,也就是基类名称 ;
* <p>
* 配置各项映射关系
*
* @param classes
* @return
*/
private void setFieldMapping(List<Class> classes) {
Class clazz = null;
for (int i = 0; i < classes.size(); i++) {
clazz = classes.get(i);
int j = 0;
FromWho fromWho = (FromWho) clazz.getAnnotation(FromWho.class);
String className = fromWho.className();
Field[] allFields = clazz.getDeclaredFields();
for (Field field : allFields) {
if (field.isAnnotationPresent(ExcelVOAttribute.class)) {
String column = field.getAnnotation(ExcelVOAttribute.class).column();
String indexName = field.getAnnotation(ExcelVOAttribute.class).name();
String regex = field.getAnnotation(ExcelVOAttribute.class).regex();
String info = field.getAnnotation(ExcelVOAttribute.class).info();
int count = getColumnIndex(column);
cellMaxNum = Math.max(count, cellMaxNum);
indexNameClassMap.put(indexName, className);
indexNameFieldMap.put(indexName, field);
indexNameRegex.put(field, regex);
indexNameErrorInfo.put(field, info);
}
}
if (clazz.getSuperclass() != null
&& !clazz.getSuperclass().equals(Object.class)) {
List<Class> list = new ArrayList<>();
list.add(clazz.getSuperclass());
setFieldMapping(list);
}
}
}
涉及到的关系如下:
/**
* 将字段进行分拣保持在其中,按照其在excel的列索引;
*/
private Map<Integer, String> indexClassMap = new HashMap<>();
/**
* 字段与列索引之间的关系
*/
private Map<String, Field> indexNameFieldMap = new HashMap<>();
/**
* 保存配置文件的 class 对象
*/
private Map<String, Class> name4class = new HashMap<>();
/**
* 列名字与类的映射关系
*/
private Map<String, String> indexNameClassMap = new HashMap<>();
/**
* 列与校验规则的映射关系
*/
private Map<Field, String> indexNameRegex = new HashMap<>();
/**
* 错误配置信息
*/
private Map<Field, String> indexNameErrorInfo = new HashMap<>();
/**
* 列内容的错误信息
*/
private List<String> indexErrors = new ArrayList<>();
/**
* 最大单元格数
*/
private int cellMaxNum = 0;
/**
* 列索引与字段的映射
*/
private Map<Integer, String> indexFieldMap = new HashMap<>();
后记
具体实现的代码太长了,一个导入 500
多行,就不放上来,我将它们封为一个 jar
包了;
下载地址 :Excel工具类 jar 包
我自己的东西,我竟然不能设置为免费下载,最低 1 积分…