利用java反射方式实现导入excel

mac2025-08-03  1

在做项目过程中,导入excel数据应该是很常见的操作,我们都是如何去做他呢,肯定做法是多种多样的,我估计大多数同学都习惯这样的一种方式,直接对我们的对我们所需要的实体bean进行挨个set值,如下面这种方式:

定义日期格式:

private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

 正常编码风格:

public static List<ExcelImportBean> customConvertToList(InputStream inputStream) { List<ExcelImportBean> list = new ArrayList<>(); try { //创建工作簿 Workbook workbook = WorkbookFactory.create(inputStream); //创建工作表sheet Sheet sheet = workbook.getSheetAt(0); //sheet中数据的行数 int rows = sheet.getPhysicalNumberOfRows(); //表头单元格个数 int cells = sheet.getRow(0).getPhysicalNumberOfCells(); for (int i = 1; i < rows; i++) { Row row = sheet.getRow(i); ExcelImportBean bean = new ExcelImportBean(); bean.setNum(Integer.valueOf(getCellValue(row.getCell(0)))); bean.setStr(getCellValue(row.getCell(1))); bean.setDate(DATE_FORMAT.parse(getCellValue(row.getCell(2)))); list.add(bean); } } catch (Exception e) { log.error("excel parse exception"); } finally { try { inputStream.close();//流关闭 } catch (Exception e2) { log.error("io close exception"); } } return list; }

但是如果我们有多处不同对象类型的导出呢,我们就需要写多个这种代码,对我们的对象属性进行set值,看起来代码显得很那啥~~~,这样的代码怎么对他进行封装呢,我们可以利用反射对对象属性进行set值,这样我们就可以做成一个通用的工具类,方便使用,下面介绍下反射的方式的代码实现:

定义缓存(用于存储反射创建的方法信息):

private static final Map<String,Object> DATA_CACHE = new ConcurrentHashMap<>();

将excel模板数据转换为list:

/** *@desc: 模板数据转换为list * *@date: 下午5:00 */ public static <T> List<T> convertToList(Class<T> clazz, InputStream inputStream) { List<T> list = new ArrayList<>(); try { //创建工作簿 Workbook workbook = WorkbookFactory.create(inputStream); //创建工作表sheet Sheet sheet = workbook.getSheetAt(0); //sheet中数据的行数 int rows = sheet.getPhysicalNumberOfRows(); //表头单元格个数 int cells = sheet.getRow(0).getPhysicalNumberOfCells(); Field[] fields = clazz.getDeclaredFields(); for (int i = 0;i<cells;i++){ Field field = fields[i]; String methodName = buildMethodName(field.getName()); DATA_CACHE.put(field.getName(),methodName); DATA_CACHE.put(clazz.getName()+field.getName(),clazz.getMethod(methodName, new Class[]{field.getType()})); } for (int i = 1; i < rows; i++) { Row row = sheet.getRow(i); T o = clazz.getConstructor(new Class[]{}).newInstance(new Object[]{}); for(int index = 0;index < cells;index ++){ Cell cell = row.getCell(index); if(cell == null){ cell = row.createCell(index); } Field field = fields[index]; String methodName = (String) DATA_CACHE.get(field.getName()); Method method = (Method) DATA_CACHE.get(clazz.getName()+field.getName()); setAttrValue(method,o, getCellValue(cell),field.getType()); } list.add(o); } } catch (Exception e) { log.error("excel parse exception"); } finally { try { inputStream.close();//流关闭 } catch (Exception e2) { log.error("io close exception"); } } return list; }

set方法构建:

/** * @desc: 构建set方法名称 * @auther: Tjs * @date: 下午2:12 */ private static String buildMethodName(String attr) { Pattern pattern = Pattern.compile("[a-zA-Z]"); Matcher matcher = pattern.matcher(attr); StringBuilder sb = new StringBuilder("set"); if (matcher.find() && attr.charAt(0) != '_') { sb.append(matcher.replaceFirst(matcher.group().toUpperCase())); } else { sb.append(attr); } return sb.toString(); }

设置属性值:

/** * @desc: 设置属性值 * @auther: Tjs * @date: 下午2:12 */ private static void setAttrValue(Method method,Object o,String val,Class<?> type) { try { //参数类型转换 把String放到第一个if里是因为我们导入的数据大都是字符串类型,这样可以减少不必要的判断次数,当数据量很大时候可以体现出来 if(type == java.lang.String.class){ method.invoke(o, type.cast(val)); }else if (type == java.util.Date.class) { Date date = null; try { date = DATE_FORMAT.parse(val); } catch (Exception e) { log.error("日期转换错误"); } method.invoke(o, date); } else if (type == int.class || type == java.lang.Integer.class || type == long.class || type == java.lang.Long.class) { val = val.substring(0, val.lastIndexOf(".") == -1 ? val.length() : val.lastIndexOf(".")); method.invoke(o, Integer.valueOf(val)); } else if (type == float.class || type == java.lang.Float.class) { method.invoke(o, Float.valueOf(val)); } else if (type == double.class || type == java.lang.Double.class) { method.invoke(o, Double.valueOf(val)); } else if (type == byte.class || type == java.lang.Byte.class) { method.invoke(o, Byte.valueOf(val)); } else if (type == boolean.class || type == java.lang.Boolean.class) { method.invoke(o, Boolean.valueOf(val)); } else { method.invoke(o, type.cast(val)); } } catch (Exception e) { log.error("参数转换错误"); } }

获取列值:

/** * @desc: 获取列值 * @auther: Tjs * @date: 下午2:12 */ private static String getCellValue(Cell cell) { switch (cell.getCellType()) { case Cell.CELL_TYPE_STRING: return cell.getStringCellValue(); case Cell.CELL_TYPE_NUMERIC: if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) { Date date = cell.getDateCellValue(); return DATE_FORMAT.format(date); } else { return NumberToTextConverter.toText(cell.getNumericCellValue()); } case Cell.CELL_TYPE_BOOLEAN: Object retBool = cell.getBooleanCellValue(); return retBool.toString(); case Cell.CELL_TYPE_FORMULA: if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) { Date date = cell.getDateCellValue(); return DATE_FORMAT.format(date); } return cell.getCellFormula(); case Cell.CELL_TYPE_ERROR: Object retError = cell.getErrorCellValue(); return retError.toString(); case Cell.CELL_TYPE_BLANK: return ""; default: return cell.toString(); } }

ExcelImportBean对象:

@Data @ToString @AllArgsConstructor @NoArgsConstructor @Builder public class ExcelImportBean { private Integer num; private String str; private Date date; private String other; }

 调用:

@ApiOperation("导入") @PostMapping("/import") public Result excelImport(@RequestParam MultipartFile file) throws IOException { List<ExcelImportBean> list = ExcelUtil.convertToList(ExcelImportBean.class,file.getInputStream()); //业务处理list return new Result(); }

这样就可以愉快的使用了,很简单。

 在此我们需要注意的是我们的对象bean:ExcelImportBean中的字段属性设置要按照我们需要导出的列的顺序设置(这是约定)

 即:我们的对象的第一条属性须是数字-num,第二条须是字符串str,第三条须是日期-date,对象的其他不需要导入的字段排在这三个字段之后即可。

 

反射的方式虽然使用起来简单,不过当数据量很大的时候,性能会低,没有硬编码的方式快,为什么呢,反射我们应该知道,java运行状态下,可以获取类的相关信息,信息是通过查找获取的,需要花费点时间(当然这个时间很短),而我们正常通过new 对象的方式呢,那是在jvm编译的时候类的相关属性及方法都放在堆栈里了,直接拿来用就行了(可以少走弯路,直奔主题),数据量少我们可以忽略反射带来的影响,当很大时候用反射每次都需要走那么多的路,一次两次没事,量级上十万百万时候我们就能看出来了,速度会比硬编码的慢,慢是以内单纯的反射构建占其中一部分原因(利用缓存可以解决,上面的方式已经加了缓存了),另一部分原因是因为多了层循环(遍历的是excel的列,对我们的对象属性进行赋值),还有类型判断的方法,因为我们不知道对象属性的类型,因此在赋值时需要判断属性的类型然后进行相应的类型转换。

总之,我们在使用时,根据实际的业务场景,来选择合理的方式,大数据量数据导入,可以利用多线程来进行处理,有兴趣的同学可以自行研究~.~。

最新回复(0)