RuoYi
文章目录
- Excel案例
- Poi简介
- Java有三个类可使用
- 主要内容
- Poi封装的对象
- 从Excel文件读取数据
- 步骤
- 案例
- 向Excel文件写入数据
- 步骤
- 案例
- Excel若依
- 简介
- 使用
- ExcelUtil
- 导出
- init()
- createExcelField()
- createWorkbook()
- createTitle()
- createSubHead()
- exportExcel()
- exportExcel(HttpServletResponse response)
- exportExcel()
- fillExcelData()
- 导入
- importExcel()
- invokeSetter()
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.namethis.fields = getFields();// 似乎是按照Excel注解信息来排序,然后重新赋值this.fields = this.fields.stream().sorted(Comparatorparing(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标识的属性等;这里设置为truefield.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和subFieldsif (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 * 20public 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//创建所有的样式,赋值给stylesthis.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)){//合并后开始行数,默认为1subMergedFirstRowNum++;//合并后最后行数,默认为0subMergedLastRowNum++;//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{//创建写入数据到SheetwriteSheet();//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){//开始行,最小0int 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;//根据文件输入流创建工作薄赋值给wbthis.wb = WorkbookFactory.create(is);List<T> list = new ArrayList<T>();// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheetSheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);if (sheet == null){throw new IOException("文件sheet不存在");}//判断传入excel是老版还是新版,老版 false 新版 trueboolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);//key:图片单元格索引(1_1)Map<String, PictureData> pictures;//判断版本,传入不同方法,获取图片流PictureDataif (isXSSFWorkbook){pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);}else{pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);}// 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1int 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);//如果当前单元格不是空,就把单元格内容和其列索引存入cellMapif (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);}//如果当前列的类型不是nullif (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 });}}}

发布评论