因项目需求,Excel模板存在多处行合并和列合并,需要导入指定单元格的数据,并且存在多行数据相同情况;同时不知道后续Excel模板是否会变动。所以利用POI、注解和反射实现一个工具类,需要维护与单sheet页对应的Java Bean上的注解。
一、注解:
1、指定单元格注解,指定行、列、数据类型
/**
* Excel单元格
*/
@Documented
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCell {
/**
* excel行数
*
* @return 行数
*/
int row() default 0;
/**
* excel列数
*
* @return 列数
*/
int col() default 0;
/**
* excel单元格对应java的数据类型
*
* @return 数据类型
*/
String type() default "String";
}
2、指定开始行和结束行
@Documented
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelRow {
/**
* excel开始行
*
* @return 开始行
*/
int startRow();
/**
* excel结束行
*
* @return 结束行
*/
int endRow();
}
二、工具类
大概思路:
利用反射获取Model的属性,根据属性上的注解(ExcelCell)得到该属性对应sheet页哪个单元格和数据类型,然后获取单元格的数据并放到Model中;如果单元格中存在某几行的数据可以抽取成一个SubModel的列表,此时根据注解(ExcelRow)得到行范围,根据SubModel和行范围获取到一个数据集并放到Model中。
public class ExcelImportUtil {
/**
* 根据单元格的row和col导入一个sheet页的数据
* <p>
* 一个sheet页对应一个Java bean
* 每个属性上添加@ExcelCell注解,指定col、row和数据类型
* <p>
* 如果当前sheet页有多行相同数据,
* 可以在Java bean中添加相对应的List并添加注解@ExcelRow指定开始行和结束行,List中的bean每个属性上需要添加注解@ExcelCell指定所属列
*
* @param clazz 一个sheet页的数据类型
* @param subClazz sheet页中多行重复数据类型
* @param sheet 一sheet页
* @return 一个sheet页的数据
* @throws Exception
*/
public static Object getSheetData(Class<?> clazz, Class<?> subClazz, XSSFSheet sheet) throws Exception {
Object model = clazz.newInstance();
// 获取所有属性
Field[] fields = model.getClass().getDeclaredFields();
for (Field field : fields) {
// 获取属性注解
ExcelCell excelCell = field.getAnnotation(ExcelCell.class);
if (excelCell != null) {
// 根据注解的行和列获取指定单元格
XSSFCell cell = sheet.getRow(excelCell.row()).getCell(excelCell.col());
if (cell != null) {
getCellData(excelCell, field, cell, model);
}
} else if (subClazz != null) {
// sheet页中存在多行数据可以存放到同一类型的subBean中的情况
ExcelRow excelRow = field.getAnnotation(ExcelRow.class);
if (excelRow != null) {
List<Object> subModels = new ArrayList<>();
// 根据属性注解上的行范围获取数据并存放到bean中
for (int i = excelRow.startRow(); i <= excelRow.endRow(); i++) {
Object subModel = subClazz.newInstance();
Field[] subFields = subModel.getClass().getDeclaredFields();
for (Field subField : subFields) {
ExcelCell subCell = subField.getAnnotation(ExcelCell.class);
XSSFCell cell = sheet.getRow(i).getCell(subCell.col());
if (cell != null) {
getCellData(subCell, subField, cell, subModel);
}
}
subModels.add(subModel);
}
field.setAccessible(true);
field.set(model, subModels);
}
}
}
return model;
}
/**
* 获取单元格中的数据并存放到model中
* @param excelCell 单元格注解
* @param field 属性
* @param cell 单元格
* @param model bean
* @throws Exception
*/
private static void getCellData(ExcelCell excelCell, Field field, XSSFCell cell, Object model) throws Exception {
switch (excelCell.type()) {
case "String":
String stringCellValue = cell.getStringCellValue();
field.setAccessible(true);
field.set(model, stringCellValue);
break;
case "Integer":
double age = cell.getNumericCellValue();
field.setAccessible(true);
field.set(model, (int) age);
break;
case "Double":
double salary = cell.getNumericCellValue();
field.setAccessible(true);
field.set(model, salary);
break;
case "Date":
Date birthday = cell.getDateCellValue();
field.setAccessible(true);
field.set(model, birthday);
}
}
}
三、使用示例:
1、Excel模板
第一行数据与Model中的属性对应
二至四行数据与SubModel中的属性对应
2、声明一个sheet对应的bean
默认行为0、列为0、数据类型为String
@Getter
@Setter
@ToString
public class Model {
@ExcelCell()
private String name;
@ExcelCell(col = 1,type = "Integer")
private Integer age;
@ExcelCell(col = 2,type = "Double")
private Double salary;
@ExcelCell(col = 3,type = "Date")
private Date birthday;
@ExcelRow(startRow = 1,endRow = 2)
private List<SubModel> subModels;
}
3、声明sheet页中多行相似subBean
不需要声明行,只需要声明列和数据类型
@Getter
@Setter
@ToString
public class SubModel {
@ExcelCell()
private String name;
@ExcelCell(col = 1,type = "Integer")
private Integer age;
@ExcelCell(col = 2,type = "Double")
private Double salary;
@ExcelCell(col = 3,type = "Date")
private Date birthday;
}
4、工具类方法调用
public static void main(String[] args) throws Exception {
File file = new File("C:\\Users\\swt\\Desktop\\测试.xlsx");
XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(file));
XSSFSheet sheet = workbook.getSheetAt(0);
Model model = (Model) ExcelImportUtil.getSheetData(Model.class, SubModel.class, sheet);
System.out.println(model.getName());
System.out.println(model.getAge());
System.out.println(model.getSalary());
System.out.println(model.getBirthday());
model.getSubModels().forEach(System.out::println);
}
5、输出:
张三
18
32.25
Sun Sep 12 00:00:00 CST 2021
SubModel(name=李四, age=19, salary=32.25, birthday=Mon Jul 12 00:00:00 CST 2021)
SubModel(name=王五, age=20, salary=32.75, birthday=Wed Sep 15 00:00:00 CST 2021)