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

在做项目过程中,导入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的列,对我们的对象属性进行赋值),还有类型判断的方法,因为我们不知道对象属性的类型,因此在赋值时需要判断属性的类型然后进行相应的类型转换。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章