利用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的列,對我們的對象屬性進行賦值),還有類型判斷的方法,因爲我們不知道對象屬性的類型,因此在賦值時需要判斷屬性的類型然後進行相應的類型轉換。

總之,我們在使用時,根據實際的業務場景,來選擇合理的方式,大數據量數據導入,可以利用多線程來進行處理,有興趣的同學可以自行研究~.~。

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