文章目录
Excel案例
Poi简介
Apache POI [1] 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。
HSSF一读写Microsoft Excel XLS
xSSF一读写Microsoft Excel OOXML XLSX
HWPF一读写Microsoft Word Doc
HSLF一提供读写Microsoft PowerPoint
Java有三个类可使用
HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,扩展名是.xls;
XSSFWorkbook:是操作Excel2007后的版本,扩展名是.xlsx;
SXSSFWorkbook:是操作Excel2007后的版本,扩展名是.xlsx;
主要内容
Poi封装的对象
- XSSFWorkbook;工作簿(即整个excel文件)
- XSSFSheet:工作表(即excel文件的一个选项卡,选项卡在可视化界面下方)
- Row:行(即一整行)
- Cell:单元格(即一格)
从Excel文件读取数据
步骤
- 创建工作簿
- 获取工作表
- 遍历工作表获得行对象
- 遍历行对象获取单元格对象
- 获得单元格中的值
案例
public class Demo01 {
public static void main(String[] args) throws IOException {
//1. 获取工作簿
XSSFWorkbook sheets = new XSSFWorkbook("C:\\Users\\L\\Desktop\\the test.xlsx");
//2. 获取工作表
//sheets.getSheet(); 根据选项卡名称,即工作表名称
//根据索引获取工作表
XSSFSheet sheetAt = sheets.getSheetAt(0);
//3.1. 获取行
for (Row cells : sheetAt) {
//4.1. 获取单元格
for (Cell cell : cells) {
//获取单元格中的值
String value = cell.getStringCellValue();
System.out.println(value);
}
}
//5. 释放资源
sheets.close();
}
}
其中第三步和第四步可以用普通for循环(主要是需要获取索引)
//3.2. 获取行
//获取最后一行索引
int lastRowNum = sheetAt.getLastRowNum();
for (int i = 0; i<lastRowNum; i++) {
//获取当前行数据
XSSFRow row = sheetAt.getRow(i);
if (row != null) {
//获取当前行最后一个单元格索引
short lastCellNum = row.getLastCellNum();
for (int j = 0; j < lastCellNum; j++) {
//获取当前党员格数据
XSSFCell cell = row.getCell(j);
if (cell != null) {
String stringCellValue = cell.getStringCellValue();
System.out.println(stringCellValue);
}
}
}
}
向Excel文件写入数据
步骤
- 创建一个Excel文件
- 创建工作表
- 创建行
- 创建单元格赋值
- 通过输出流将对象下载到磁盘
案例
public class Demo02 {
public static void main(String[] args) throws IOException {
//1. 创建工作薄
XSSFWorkbook sheets = new XSSFWorkbook();
//2. 创建工作表
XSSFSheet sheet = sheets.createSheet("工作表一");
//3. 创建行
for (int i = 0; i < 3; i++) {
XSSFRow row = sheet.createRow(i);
//4. 创建单元格
row.createCell(0).setCellValue("I" + i);
row.createCell(1).setCellValue("Love" + i);
row.createCell(2).setCellValue("You" + i);
}
//5. 创建输出流
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\L\\Desktop\\the hh.xlsx");
//将内容写入文件
sheets.write(fileOutputStream);
//刷新
fileOutputStream.flush();
//6. 释放资源
fileOutputStream.close();
sheets.close();
System.out.println("写入成功");
}
}
Excel若依
简介
若依中一共有两个Excel注解,分别是@Excel
和@Excels
;后者是前者的数组集
注解路径:/ruoyi-common/src/main/java/com/ruoyi/common/annontation
(两个都在包内)
实现类路径:/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil
使用
// 单个字段导出
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT)
private Dept dept;
// 多个字段导出
@Excels({
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
})
private Dept dept;
ExcelUtil
路径:/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil
导出
导出就是把数据库查出的数据从集合转为Excel文件的过程(当然还要发给客户端或者直接下载下来)
init()
调用导出方法,最终都会调用init
方法
作用:最终会给
ExcelUtil
类的几个属性赋值;分别是list
,sheetName
,type
,title
,fields
,maxHeight
,subMethod
(依情况而定),subFields
(依情况而定),wb
,sheet
,styles
public void init(List<T> list, String sheetName, String title, Type type)
{
if (list == null)
{
list = new ArrayList<T>();
}
//list是数据列表,就是要操作的主体
this.list = list;
//工作表名称
this.sheetName = sheetName;
//导出类型(EXPORT:导出数据;IMPORT:导入模板),不过还有个枚举(ALL(0):导出导入)
this.type = type;
//标题,应该是表中数据第一行,会
this.title = title;
createExcelField();
createWorkbook();
createTitle();
createSubHead();
}
createExcelField()
作用:得到所有定义字段的信息(fields
是注解列表,maxHeight
是最大高度)
private void createExcelField()
{
// List<Object[]> fields 注解列表;Object[]中包含某个实体类属性的信息(Field)和其注解信息
// 实体类属性的信息:
// 例如:
// private static String name;
// private static java.lang.String com.lovli.other.test.name
this.fields = getFields();
// 似乎是按照Excel注解信息来排序,然后重新赋值
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
// 获取最大行高
this.maxHeight = getRowHeight();
}
-
getFields()
封装了获取字段注解信息的过程作用:传入
ExcelUtil
的实体类,获取头上带有俩注解之一的属性(包括父类的),经过一点逻辑,然后返回赋值给ExcelUtil
的fields属性;同时,如果实体类中有集合属性存在,也会给subMethod
和subFields
赋值public List<Object[]> getFields() { List<Object[]> fields = new ArrayList<Object[]>(); //保存当前实体类所有的字段,包括继承的父类的字段 List<Field> tempFields = new ArrayList<>(); //getSuperclass() 这个方法的返回类型是Class,它返回此对象表示的实体的超类 //getDeclaredFields() 此方法的返回类型为Field[] ,它返回一个Field对象数组,该数组表示该类的所有已声明字段(不包括继承的Fields) //这两行意思就是:上一行获取当前类父类的所有已声明字段,下一行获取当前类自身的所有已声明字段 tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); for (Field field : tempFields) { //excludeFields是隐藏的属性(就是程序员要主动忽略的某个实体属性) //判断当前字段是否在需要隐藏属性的数组中,不在的话再进行下一步,否则就进入下一次循环 if (!ArrayUtils.contains(this.excludeFields, field.getName())) { //isAnnotationPresent() 指定类型的注释存在于此元素上返回true,否则false //当前属性头上有没有@Excel或者@Excels注解,有继续,没有结束 // 单注解 if (field.isAnnotationPresent(Excel.class)) { //Excel 注释类的指定对象 Excel attr = field.getAnnotation(Excel.class); if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) { //设置权限 //通俗讲就是为true时,不会去检查java语言权限控制,可以获取到private标识的属性等;这里设置为true field.setAccessible(true);、 fields.add(new Object[] { field, attr }); } //a.isAssignableFrom(b) //满足下方条件其一返回true,否则返回false //(1)a对象所对应类信息是b对象所对应的类信息的父类或者是父接口,简单理解即a是b的父类或接口 //(2)a对象所对应类信息与b对象所对应的类信息相同,简单理解即a和b为同一个类或同一个接口 //字面意思:判断当前字段类型是不是包含在集合中,即判断是不是集合的一种 //这个if就是如果当前字段是集合,获取跟该字段有关的方法和属性信息的集合分别赋值给subMethod和subFields if (Collection.class.isAssignableFrom(field.getType())) { //getSubMethod() 根据字段名字(字段名字会拼接,最终是要获得的方法的名字)最终获得该类声明的公开的方法 //subMethod(对象的子列表方法)是成员变量,给其赋值 subMethod = getSubMethod(field.getName(), clazz); //getType():返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型 //getGenericType():返回一个 Type 对象,它表示此 Field 对象所表示字段的声明类型 //例子: //Field:name (String类型) Field:type (T类型) //Type: Type: // java.lang.String java.lang.Object //GenericType: GenericType: // class java.lang.String T //获得当前字段的类型 ParameterizedType pt = (ParameterizedType) field.getGenericType(); //getActualTypeArguments()[0] 从一个泛型类型中获取第一个泛型参数的类型类 Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0]; //返回的是字段上有指定注解的字段,返回一个字段集合赋给subFields(对象的子列表属性) this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); } } // 多注解 if (field.isAnnotationPresent(Excels.class)) { Excels attrs = field.getAnnotation(Excels.class); //与上方单注解类似,只是把多注解遍历了一下 Excel[] excels = attrs.value(); for (Excel attr : excels) { if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) { field.setAccessible(true); fields.add(new Object[] { field, attr }); } } } } } return fields; }
-
stream()
流的作用//1. sorted() 默认使用自然序排序, 其中的元素必须实现Comparable 接口 //2. sorted(Comparator<? super T> comparator) :我们可以使用lambada 来创建一个Comparator 实例。可以按照升序或着降序来排序元素
-
getRowHeight()
遍历所有注解,找出最大行高并返回maxHeight * 20
public short getRowHeight() { double maxHeight = 0; //遍历所有字段,获取其注解信息,然后找到最大的height属性,最后返回 for (Object[] os : this.fields) { //os[1],即获取注解信息 Excel excel = (Excel) os[1]; maxHeight = Math.max(maxHeight, excel.height()); } return (short) (maxHeight * 20); }
createWorkbook()
作用:创建一个工作薄(wb
是工作薄对象xlsx
版,sheet
是工作表对象,styles
是样式列表(map集合))
public void createWorkbook()
{
this.wb = new SXSSFWorkbook(500);
//Workbook已有的创建工作表的方法
this.sheet = wb.createSheet();
//给第一个(索引)工作表命名
wb.setSheetName(0, sheetName);
//Map<String, CellStyle> styles
//创建所有的样式,赋值给styles
this.styles = createStyles(wb);
}
-
new SXSSFWorkbook(500)
创建一个工作薄对象,会限制行数为500//当rowAccessWindowSize达到限定值时,新一行数据的加入会引起老一行的数据刷新到硬盘 //就是当行号超过500,达到501时,会把0的记录刷新到硬盘并从内存中删除 public SXSSFWorkbook(int rowAccessWindowSize){}
-
createStyles()
创建表格样式,如某行单元格的大小、字体等private Map<String, CellStyle> createStyles(Workbook wb) { // 写入各条记录,每条记录对应excel表中的一行 Map<String, CellStyle> styles = new HashMap<String, CellStyle>(); // 创建一种单元格样式,命名title存入map中 CellStyle style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); Font titleFont = wb.createFont(); titleFont.setFontName("Arial"); titleFont.setFontHeightInPoints((short) 16); titleFont.setBold(true); style.setFont(titleFont); styles.put("title", style); // 创建一种单元格样式,命名data存入map中 style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setBorderRight(BorderStyle.THIN); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderLeft(BorderStyle.THIN); style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderTop(BorderStyle.THIN); style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderBottom(BorderStyle.THIN); style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); Font dataFont = wb.createFont(); dataFont.setFontName("Arial"); dataFont.setFontHeightInPoints((short) 10); style.setFont(dataFont); styles.put("data", style); // 创建一种单元格样式,命名total存入map中 style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); Font totalFont = wb.createFont(); totalFont.setFontName("Arial"); totalFont.setFontHeightInPoints((short) 10); style.setFont(totalFont); styles.put("total", style); // 获取所有字段的注解,根据注解生成单元格样式,在map中key命名都是header开头 styles.putAll(annotationHeaderStyles(wb, styles)); // 同上,不过key命名都是data开头 styles.putAll(annotationDataStyles(wb)); return styles; }
createTitle()
作用:创建excel第一行标题
public void createTitle()
{
if (StringUtils.isNotEmpty(title))
{
//合并后开始行数,默认为1
subMergedFirstRowNum++;
//合并后最后行数,默认为0
subMergedLastRowNum++;
//titleLastCol是字段数量的索引
int titleLastCol = this.fields.size() - 1;
//如果subFields有值(可以理解为,如果需要导出的实体类有集合存在)
if (isSubList())
{
titleLastCol = titleLastCol + subFields.size() - 1;
}
//rownum始终为1或者0;创建第一行,即一般的标题行
Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
//设置的值永远是height属性值的30倍
titleRow.setHeightInPoints(30);
//创建该行索引为0的单元格
Cell titleCell = titleRow.createCell(0);
//从styles(一个存储了表格样式的map集合)中获取样式,赋予这个单元格
titleCell.setCellStyle(styles.get("title"));
//标题赋予这个单元格
titleCell.setCellValue(title);
//addMergedRegion合并单元格
//CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
//这里是索引
//起始行 结束行 起始列 结束列
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol));
}
}
createSubHead()
作用:创建对象的子列表名称
public void createSubHead()
{
//判断是否有子列表
if (isSubList())
{
//合并后开始行数,默认为1(此时必然不为1了)
subMergedFirstRowNum++;
//合并后最后行数,默认为0(此时必然不为0了)
subMergedLastRowNum++;
//在当前行号创建行,一般就是标题的下一行了,属性行
Row subRow = sheet.createRow(rownum);
int excelNum = 0;
for (Object[] objects : fields)
{
Excel attr = (Excel) objects[1];
//根据循环来创建第几个单元格,并附上名字和样式
Cell headCell1 = subRow.createCell(excelNum);
headCell1.setCellValue(attr.name());
headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
excelNum++;
}
int headFirstRow = excelNum - 1;
int headLastRow = headFirstRow + subFields.size() - 1;
//可以理解为:如果有集合存在
if (headLastRow > headFirstRow)
{
//合并当前行的单元格(给集合使用的单元格)
//看样子默认放在excel最右列了
sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow));
}
rownum++;
}
}
exportExcel()
在init()
方法之后,最后都会调用该方法,该方法有两个,一个带参数,一个不带参数
作用:根据
init()
方法初始化的属性,来创建excel表,并填入数据
两者区别:
exportExcel(HttpServletResponse response)
利用servlet容器,把文件响应给客户端exportExcel()
根据配置文件路径,下载文件到指定路径下
exportExcel(HttpServletResponse response)
目前框架已有的导入导出都使用的该方法
//servlet容器针对本次请求,创建了一个response对象,然后作为参数传给controller层,然后传入该方法
//其中封装了HTTP相应消息
public void exportExcel(HttpServletResponse response)
{
try
{
//创建写入数据到Sheet
writeSheet();
//workbook的方法,用来将写入工作薄中
//只要有一个流(getOutputStream()方法)被创建了,并且已经完成了流的输出那么servlet容器就会将response对象交给服务器。服务器将response对象中的内容做拆解响应给客户端。
wb.write(response.getOutputStream());
}
catch (Exception e)
{
log.error("导出Excel异常{}", e.getMessage());
}
finally
{
//用来释放资源
IOUtils.closeQuietly(wb);
}
}
-
writeSheet()
创建工作表做好样式并填入数据,可以说是主体了public void writeSheet() { // 取出一共有多少个sheet. // sheetSize是当前工作表的最大行数,当超过最大行数时,才会有多个工作表,否则就一个 int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); for (int index = 0; index < sheetNo; index++) { //该方法就是创建工作表 //条件是:当sheetNo大于1并且index大于0时才会创建新的工作表 //可以理解就是没有超过sheetSize限制数量的数据时,不需要创建新的工作表 createSheet(sheetNo, index); // 产生一行 Row row = sheet.createRow(rownum); int column = 0; // 写入各个字段的列头名称 for (Object[] os : fields) { //获取当前属性的信息和注解信息 Field field = (Field) os[0]; Excel excel = (Excel) os[1]; //判断该字段是否是集合 //是集合就根据集合内属性创建单元格样式,填充内容;集合本身的名字是父标题 if (Collection.class.isAssignableFrom(field.getType())) { for (Field subField : subFields) { Excel subExcel = subField.getAnnotation(Excel.class); //创建header单元格的方法 this.createHeadCell(subExcel, row, column++); } } else { this.createHeadCell(excel, row, column++); } } if (Type.EXPORT.equals(type)) { //这个方法就是填充所有数据到工作表中的方法 fillExcelData(index, row); //创建统计列表 //在每一个工作表数据的最后一行的后面新建一行,用来统计该行之前的数据 addStatisticsRow(); } } }
exportExcel()
目前框架内还没使用
public AjaxResult exportExcel()
{
OutputStream out = null;
try
{
//同上方法
writeSheet();
//生成文件名
String filename = encodingFilename(sheetName);
//创建输出流,getAbsoluteFile(filename)返回下载路径;路径可以在ruoyi-admin模块下的配置文件中更改
out = new FileOutputStream(getAbsoluteFile(filename));
//写入数据,并生成文件在指定路径下
wb.write(out);
return AjaxResult.success(filename);
}
catch (Exception e)
{
log.error("导出Excel异常{}", e.getMessage());
throw new UtilException("导出Excel失败,请联系网站管理员!");
}
finally
{
//释放两个资源
IOUtils.closeQuietly(wb);
IOUtils.closeQuietly(out);
}
}
fillExcelData()
解释:上述方法中writeSheet()
下填充所有数据到工作表中的方法
//注解 压制警告 index 当前工作表的索引 row 当前行(应该是属性行)
@SuppressWarnings("unchecked")
public void fillExcelData(int index, Row row)
{
//开始行,最小0
int startNo = index * sheetSize;
//结束行
int endNo = Math.min(startNo + sheetSize, list.size());
//当前行,这里减startNo,是为了下面for循环,是为了不同工作表考虑;可以理解为当前当前工作表的当前行,不会超过限制行数
int rowNo = (1 + rownum) - startNo;
//下面就是遍历每一行,然后再遍历每一列,给每个单元格填入数据,不再解释了
for (int i = startNo; i < endNo; i++)
{
rowNo = i > 1 ? rowNo + 1 : rowNo + i;
row = sheet.createRow(rowNo);
// 得到导出对象.
T vo = (T) list.get(i);
Collection<?> subList = null;
if (isSubList())
{
if (isSubListValue(vo))
{
subList = getListCellValue(vo);
subMergedLastRowNum = subMergedLastRowNum + subList.size();
}
else
{
subMergedFirstRowNum++;
subMergedLastRowNum++;
}
}
int column = 0;
for (Object[] os : fields)
{
Field field = (Field) os[0];
Excel excel = (Excel) os[1];
if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList))
{
boolean subFirst = false;
for (Object obj : subList)
{
if (subFirst)
{
rowNo++;
row = sheet.createRow(rowNo);
}
List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
int subIndex = 0;
for (Field subField : subFields)
{
if (subField.isAnnotationPresent(Excel.class))
{
subField.setAccessible(true);
Excel attr = subField.getAnnotation(Excel.class);
this.addCell(attr, row, (T) obj, subField, column + subIndex);
}
subIndex++;
}
subFirst = true;
}
this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size();
}
else
{
this.addCell(excel, row, vo, field, column++);
}
}
}
}
导入
importExcel()
调用导入方法,最终都是调用该方法(巨长)
作用:就是把excel表格不为空的列和实体类比较;然后类型转换,把工作薄完全转成集合,最终继续其他方法,将之导入数据库
//sheetName 表格索引名,默认为无 is 文件输入流 titleNum 标题占用行数
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
{
//类型设置为仅导入
this.type = Type.IMPORT;
//根据文件输入流创建工作薄赋值给wb
this.wb = WorkbookFactory.create(is);
List<T> list = new ArrayList<T>();
// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet
Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
if (sheet == null)
{
throw new IOException("文件sheet不存在");
}
//判断传入excel是老版还是新版,老版 false 新版 true
boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);
//key:图片单元格索引(1_1)
Map<String, PictureData> pictures;
//判断版本,传入不同方法,获取图片流PictureData
if (isXSSFWorkbook)
{
pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);
}
else
{
pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);
}
// 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1
int rows = sheet.getLastRowNum();
if (rows > 0)
{
// 定义一个map用于存放excel列的序号和field.
Map<String, Integer> cellMap = new HashMap<String, Integer>();
// 获取表头
Row heard = sheet.getRow(titleNum);
// getPhysicalNumberOfCells()获取不为空的列个数
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
{
Cell cell = heard.getCell(i);
//如果当前单元格不是空,就把单元格内容和其列索引存入cellMap
if (StringUtils.isNotNull(cell))
{
String value = this.getCellValue(heard, i).toString();
cellMap.put(value, i);
}
else
{
cellMap.put(null, i);
}
}
// 有数据时才处理 得到类的所有field.
List<Object[]> fields = this.getFields();
Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
for (Object[] objects : fields)
{
Excel attr = (Excel) objects[1];
//获取当前属性所对应的excel列的索引
Integer column = cellMap.get(attr.name());
if (column != null)
{
fieldsMap.put(column, objects);
}
}
//从头行下一行到末行遍历
for (int i = titleNum + 1; i <= rows; i++)
{
// 从第2行开始取数据,默认第一行是表头.
Row row = sheet.getRow(i);
// 判断当前行是否是空行
if (isRowEmpty(row))
{
continue;
}
T entity = null;
//遍历所有不为空的列
for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet())
{
//获取当前行当前列的单元格的值
Object val = this.getCellValue(row, entry.getKey());
// 如果不存在实例则新建.
entity = (entity == null ? clazz.newInstance() : entity);
// 从map中得到对应列的field.
Field field = (Field) entry.getValue()[0];
Excel attr = (Excel) entry.getValue()[1];
// 取得类型,并根据对象类型设置值.
Class<?> fieldType = field.getType();
//下面一系列判断当前列需要的数据的类型(实体类属性的类型),并给获取的单元格的值转成需要的类型
if (String.class == fieldType)
{
String s = Convert.toStr(val);
if (StringUtils.endsWith(s, ".0"))
{
//从".0"第一次出现的位置向前截取
val = StringUtils.substringBefore(s, ".0");
}
else
{
//如果注解有日期格式,就根据日期格式来更新字符串
String dateFormat = field.getAnnotation(Excel.class).dateFormat();
if (StringUtils.isNotEmpty(dateFormat))
{
val = parseDateToStr(dateFormat, val);
}
else
{
val = Convert.toStr(val);
}
}
}
//isNumeric()检测变量是否为数字或数字字符串
else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
{
val = Convert.toInt(val);
}
else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
{
val = Convert.toLong(val);
}
else if (Double.TYPE == fieldType || Double.class == fieldType)
{
val = Convert.toDouble(val);
}
else if (Float.TYPE == fieldType || Float.class == fieldType)
{
val = Convert.toFloat(val);
}
else if (BigDecimal.class == fieldType)
{
val = Convert.toBigDecimal(val);
}
else if (Date.class == fieldType)
{
if (val instanceof String)
{
val = DateUtils.parseDate(val);
}
else if (val instanceof Double)
{
val = DateUtil.getJavaDate((Double) val);
}
}
else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
{
val = Convert.toBool(val, false);
}
//如果当前列的类型不是null
if (StringUtils.isNotNull(fieldType))
{
//获取实体类属性的名字
String propertyName = field.getName();
//根据注解上的设置来更新val(targetAttr另一个类中的属性名称,支持多级获取,以小数点隔开)
//例子:
//@Excels({
//@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
//@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
//})
//private SysDept dept;
//有targetAttr就说明当前这个属性是其他实体类的,当前属性名无法在改实体类匹配的数据表中找到
if (StringUtils.isNotEmpty(attr.targetAttr()))
{
propertyName = field.getName() + "." + attr.targetAttr();
}
else if (StringUtils.isNotEmpty(attr.readConverterExp()))
{
val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
}
else if (StringUtils.isNotEmpty(attr.dictType()))
{
val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
}
else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{
val = dataFormatHandlerAdapter(val, attr);
}
else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures))
{
PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());
if (image == null)
{
val = "";
}
else
{
byte[] data = image.getData();
val = FileUtils.writeImportBytes(data);
}
}
//propertyName是用来找方法的,匹配函数名
ReflectUtils.invokeSetter(entity, propertyName, val);
}
}
//将当前行数据转为实体类后加入集合
list.add(entity);
}
}
return list;
}
invokeSetter()
作用:如果属性需要从其他实体类中找,在这里做逻辑处理;会执行实体类中的set、get方法
//obj 当前这个实体类对象 propertyName 属性名 value 根据属性类型转换过一次类型的单元格名称(如果类型不是基本/引用类型大概率还没转换过)
public static <E> void invokeSetter(Object obj, String propertyName, E value)
{
Object object = obj;
//如果propertyName根据.分割
String[] names = StringUtils.split(propertyName, ".");
for (int i = 0; i < names.length; i++)
{
//数组应该最多存在两个内容:因为excel的一个单元格嘛
//属性名.targetAttr(only one),如果是多注解,另一个targetAttr在下次循环
//如果没有targetAttr,走下面拼接set前缀的方法
//如果有targetAttr,属性名走上面拼接get前缀的方法,targetAttr走下面拼接set前缀的方法
if (i < names.length - 1)
{
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
//根据这个get方法名,找到这个对象中的该方法,并执行;会给该属性赋值上另一个对象(容器一般会注入,再触发一次是为了避免出现null吗)
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}
else
{
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
//获取这个set方法,再获取这个方法的参数类型
//当value有值,并且value的类型和查找的方法(实体类中的set方法)的参数类型不同时,才会转型;最终执行该set方法
invokeMethodByName(object, setterMethodName, new Object[] { value });
}
}
}