使用Java泛型和反射機制編寫Excel文件生成和解析的通用工具類

前幾天被派到一個小項目中做臨時維護,工作地點不方便且不說,項目代碼那叫一個噁心...

代碼幾乎完全沒有註釋。這應該是我們天朝大部分程序員的習慣,代碼不寫註釋,給後面維護的同事帶來多大麻煩啊!
幾百行的JS代碼放在JSP文件中,而且沒有格式。個人覺得這麼長的代碼提取到JS文件中比較好,都堆在JSP中使程序可讀性極差!
HTML代碼沒有結構可言。基本的縮進都沒有,讀這種代碼那叫一個欲哭無淚啊!HTML混合JSTL以及Struts2的標籤,叫人頭大!
超長的方法體。接觸項目的第三天見到一個600多行的方法,感覺很頭大,更極品的是後來發現了一個1100行的方法,當時的感覺是見到高人了...
文件組織極其混亂。自己寫的JS與引入的開源JS框架混在一起,JS開源框架文件也是亂放。
命名也是非常混亂,完全做不到見名知義。
代碼亂放。本來不是該模塊的代碼非要組織到該模塊中...
其它問題。如同樣代碼多處編寫等
其中有一個問題就是對Excel文件的導入解析(就是那個600多行的方法),那麼長的方法,我都懶得去讀一遍;代碼長還不是主要問題,主要問題是這種代碼肯定是需要一次就得寫一次。有鑑於此,我便花些時間試着利用Java的泛型與反射機制寫了一個通用的工具類,但絞盡腦汁也沒有寫地太完善,思來想去總是不盡人意;佑於水平不高,也只能如此,測試了一下勉強能用,只是容錯能力不強,發表於此,以待後用...

列名使用粗體、水平居中、垂直居中、12號字,沒有其它樣式,也沒有提供自定義單元格樣式的功能,如果有特殊需求,此類不適用。

package com.ninemax.common.util;
 
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
 
import jxl.BooleanCell;
import jxl.Cell;
import jxl.DateCell;
import jxl.NumberCell;
import jxl.Sheet;
import jxl.Workbook;
import jxl.biff.EmptyCell;
import jxl.format.Alignment;
import jxl.format.VerticalAlignment;
import jxl.write.Blank;
import jxl.write.DateTime;
import jxl.write.Label;
import jxl.write.WritableCellFormat;
import jxl.write.WritableFont;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
 
/**
 * 操作Excel工具類(使用開源項目JXL操作Excel).<br />
 * 需要嚴格按照方法定義來使用, 容錯能力不高.<br />
 * 實體類應該嚴格按照JavaBean規範定義, 數據類型僅支持基本類型、java.util.Date、java.lang.String
 * 
 * @author zhangyh (2013-01-11)
 */
public class ExcelUtil {
    
    /**
     * getExcelStream(T[], String[], String[], String)的中轉方法, 可直接接受List集合數據
     * @see {@link ExcelUtil#getExcelStream(Object[], String[], String[], String)}
     */
    public static <T> OutputStream getExcelStream(List<T> dataList, String [] beanProperties,
            String [] columnNames, String sheetName) throws Exception {
        if (null != dataList && dataList.size() > 0) {
            Class<?> newType = dataList.get(0).getClass();
            @SuppressWarnings("unchecked")
            T [] tempArray = (T []) Array.newInstance(newType, dataList.size());
            dataList.toArray(tempArray);
            
            return getExcelStream(tempArray, beanProperties, columnNames, sheetName);
        }
        return null;
    }
    
    /**
     * getExcelStreamWithPointOutProperties(T[], String[], String[], String)的中轉方法, 
     * 可直接接受List集合數據, 方法第二個參數接受指明轉換到Excel的JavaBean屬性, 順序任意.
     * @see {@link ExcelUtil#getExcelStreamWithPointOutProperties(Object[], String[], String[], String)}
     */
    public static <T> OutputStream getExcelStreamWithPointOutProperties(List<T> dataList, 
            String [] beanProperties, String [] columnNames, String sheetName) 
            throws Exception {
        if (null != dataList && dataList.size() > 0) {
            Class<?> newType = dataList.get(0).getClass();
            @SuppressWarnings("unchecked")
            T [] tempArray = (T []) Array.newInstance(newType, dataList.size());
            dataList.toArray(tempArray);
            
            return getExcelStreamWithPointOutProperties(tempArray, beanProperties, 
                columnNames, sheetName);
        }
        return null;
    }
    
    /**
     * <p>將Java對象組成的集合轉換成Excel數據, 以輸出流形式返回, 可以實現下載、存儲在磁盤等.</p>
     * <p>Excel文件列按JavaBean屬性定義順序生成, 還需要注意Excel列名數與排除掉的屬性數之和必須與
     *     JavaBean屬性總數相等.</p>
     * @param dataList 需要轉換到Excel的Java對象數組
     * @param columnNames Excel列名(即JavaBean屬性對應的列名, 如:name >> '姓名'), 必須按JavaBean
     *     屬性定義順序給出, 還需要注意跳過excludeProperties指定的排除掉的屬性
     * @param excludeProperties 排除掉的JavaBean屬性名
     * @param sheetName Excel文件表名
     * @return 生成的Excel文件輸出流(ByteArrayOutputStream)
     * @throws Exception 
     */
    public static <T> OutputStream getExcelStream(T [] dataList, String [] columnNames, 
            String [] excludeProperties, String sheetName) throws Exception {
        if (null == columnNames) {
            throw new Exception("Excel列名必須指定.");
        }
        // 取得數組成員的類型Class對象
        Class<?> clazz = dataList.getClass().getComponentType();
        Field [] fields = clazz.getDeclaredFields();
        
        List<String> excludePropertyList = Collections.emptyList();
        if (excludeProperties != null && excludeProperties.length > 0) {
            excludePropertyList = Arrays.asList(excludeProperties);
        }
        
        if (fields.length != columnNames.length + excludePropertyList.size()) {
            throw new Exception("給定Excel列名爲" + columnNames.length + "個, 排除掉的" +
                    "屬性爲" + excludePropertyList.size() + "個, 實體類" + 
                    clazz.getSimpleName() +    "共有" + fields.length + 
                    "個屬性,個數不匹配.");
        }
        // 列名與Bean有效屬性一一對應
        String [] beanProperties = new String [columnNames.length];
        int i = 0;
        for(Field field : fields) {
            String fieldName = field.getName();
            if (excludePropertyList == null || !excludePropertyList.contains(fieldName)) {
                // 當給定排除屬性數和列名數之和與Bean屬性數不等時會有異常
                beanProperties[i++] = fieldName;
            }
        }
        Method [] getterMethods = parseGetterMethods(beanProperties, clazz);
        return getExcelStream(dataList, getterMethods, columnNames, sheetName);
    }
    
    /**
     * <p>將Java對象組成的集合轉換成Excel數據, 以輸出流形式返回, 可以實現下載、存儲在磁盤等.</p>
     * <p>可按任意順序取任意個數的Bean屬性導出到Excel, Excel列順序將與給定beanProperties順序保持一致.</p>
     * @param data 需要生成Excel文件的Java數據, beanProperties即是T中的屬性
     * @param beanProperties 需要轉換到Excel中的Bean屬性
     * @param colNames Excel使用的列名, 需要與beanProperties一一對應
     * @param sheetName Excel表名
     * @return 生成的Excel輸出流(ByteArrayOutputStream)
     * @throws Exception
     */
    public static <T> OutputStream getExcelStreamWithPointOutProperties (T [] data, 
            String [] beanProperties, String [] colNames, String sheetName) throws Exception {
        if (beanProperties == null || colNames == null) {
            throw new Exception("必須給出有效的JavaBean屬性及相應的Excel列名.");
        }
        if (beanProperties.length > colNames.length) {
            throw new Exception("給出的Excel列名爲" + colNames.length + 
            "個, 給出的有效Bean屬性爲" + beanProperties.length + "個, 個數不匹配.");
        }
        Class<?> clazz = data.getClass().getComponentType();
        Method [] getterMethods = parseGetterMethods(beanProperties, clazz);
        
        return getExcelStream(data, getterMethods, colNames, sheetName);
    }
    
    // 解析JavaBean屬性對應的getter方法
    private static Method [] parseGetterMethods(String [] properties, Class<?> clazz) 
            throws Exception {
        Method [] getterMethods = new Method [properties.length];
        int i = 0;
        for(String property : properties) {
            String getterName = "get" + property.substring(0, 1).toUpperCase() + property.substring(1);
            try {
                getterMethods[i++] = clazz.getMethod(getterName, (Class []) null);
            } catch (SecurityException e) {
                throw new Exception("屬性" + property + "對應的getter方法可能無法訪問.", e);
            } catch (NoSuchMethodException se) {
                throw new Exception("屬性" + property + "沒有對應的getter方法.", se);
            }
        }
        return getterMethods;
    }
    
    /**
     * 具體執行方法, 公開的方法getExcelStreamWithPointOutProperties和getExcelStream都是爲調用此方法作相應準備. 
     * 方法中調用getters並將值填進相應的單元格中, 最後將Excel文件以輸出流的形式返回.
     * @param data Java集合數據, 公開方法中的List形式也會轉爲數組形式.
     * @param getterMethods 需要轉換成Excel列的JavaBean屬性對應的getters方法
     * @param colNames Excel列名, 與JavaBean屬性對應
     * @param sheetName Excel表名
     * @return Excel文件對應的輸出流
     * @throws Exception
     */
    private static <T> OutputStream getExcelStream(T [] data, Method [] getterMethods, 
            String [] colNames,    String sheetName) throws Exception {
        if (null == sheetName) {
            sheetName = "Sheet_1";
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        WritableWorkbook workbook = Workbook.createWorkbook(os);
        WritableSheet sheet = workbook.createSheet(sheetName, 0);
        
        WritableCellFormat cellFormat = new WritableCellFormat();
        cellFormat.setAlignment(Alignment.CENTRE);
        cellFormat.setVerticalAlignment(VerticalAlignment.CENTRE);
        cellFormat.setFont(new WritableFont(WritableFont.ARIAL, 12, WritableFont.BOLD));
        
        int col = 0;
        for(Method method : getterMethods) {
            int row = 0;
            // 第一行(列名)採用粗體、15號字, 並居中對齊
            sheet.addCell(new Label(col, row++, colNames[col], cellFormat));    
            for(T t : data) {
                // 調用getter方法, 並將返回值添加到Excel的單元格中
                invokeGetterMethod(method, t, sheet, row++, col);    
            }
            col++;
        }
        workbook.write();
        workbook.close();
        return os;
    }
    
    /**
     * 調用JavaBean中的取值方法, 並將返回值按照JavaBean屬性的數據類型填充到Excel指定的單元格.
     * @param getterMethod JavaBean中的取值方法Method
     * @param o 調用Getter方法的JavaBean實例
     * @param sheet Excel表
     * @param rowIndex Excel表中的行索引
     * @param colIndex Excel表中的列索引
     * @throws Exception
     */
    private static void invokeGetterMethod(Method getterMethod, Object o, WritableSheet sheet,
            int rowIndex, int colIndex) throws Exception {
        Class<?> type = getterMethod.getReturnType();
        Object returnVal = getterMethod.invoke(o, (Object []) null);
        if (null == returnVal) {    // getter方法返回值爲null
            sheet.addCell(new Blank(colIndex, rowIndex));
        }else if (String.class.isAssignableFrom(type) || char.class.isAssignableFrom(type)
                || Character.class.isAssignableFrom(type)) {
            // getter方法返回值爲String或char
            sheet.addCell(new Label(colIndex, rowIndex, returnVal.toString()));
        } else if (Number.class.isAssignableFrom(type) || double.class.isAssignableFrom(type)
                || int.class.isAssignableFrom(type) || long.class.isAssignableFrom(type)
                || short.class.isAssignableFrom(type) || float.class.isAssignableFrom(type)) {
            // getter方法返回值爲數字類型
            sheet.addCell(new jxl.write.Number(colIndex, rowIndex, ((Number)returnVal).doubleValue()));
        } else if (Date.class.isAssignableFrom(type)) {        // getter方法返回值爲java.util.Date類型
            sheet.addCell(new DateTime(colIndex, rowIndex, (Date) returnVal));
        } else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) {
            // getter方法返回值爲布爾類型
            sheet.addCell(new jxl.write.Boolean(colIndex, rowIndex, (Boolean) returnVal));
        } else {    // 不支持其它的返回值類型
            throw new Exception("getter方法: " + getterMethod.getName() + 
                "的返回值類型不被支持.");
        }
    }
    
    /**
     * <p>解析Excel, 將Excel扁平數據解析封裝爲Java對象, 以Java集合形式返回.</p>
     * <p>由於封裝並不完善, 需要嚴格按照方法指定調用方式使用纔可. 解析順序必須按照Excel文件中列順序</p>
     * @param excel 需要解析的Excel文件
     * @param beanProperties JavaBean的屬性名, 以數組方式給出; 必須依照Excel文件所定義列順序給出.
     * @param columnTypes JavaBean屬性的數據類型, 必需與columns數組數據一一對應
     * @param clazz JavaBean的Class對象, 必須指定, 否則無法使用反射
     * @return Excel解析所得JavaBean組成的List
     * @throws Exception
     */
    public static <T> List<T> parseExcel(File excel, String [] beanProperties, 
            Class<?> [] propertyTypes, Class<T> clazz) throws Exception {
        return parseExcel(new FileInputStream(excel), beanProperties, propertyTypes, clazz);
    }
    
    /**
     * 解析Excel, 將Excel扁平數據解析封裝爲Java對象.
     * @param inputStream Excel文件對應的輸入流.
     * @param columns JavaBean的屬性名
     * @param columnTypes JavaBean屬性的數據類型
     * @param clazz JavaBean類的Class對象
     * @return Excel解析所得JavaBean組成的List
     * @throws Exception
     * @see {@link ExcelUtil#parseExcel(File, String[], Class[], Class)}
     */
    public static <T> List<T> parseExcel(InputStream inputStream, String [] beanProperties,
            Class<?> [] propertyTypes, Class<T> clazz) throws Exception {
        if (beanProperties == null || propertyTypes == null) {
            throw new Exception("必須指定Excel列映射的JavaBean屬性及相應的數據類型.");
        }
        if (beanProperties.length > propertyTypes.length) {
            throw new Exception("給定的JavaBean屬性爲" + beanProperties.length + 
                "個, 數據類型爲" + propertyTypes.length + "個, 個數不匹配.");
        }
        Method [] setMethods = new Method [beanProperties.length];
        int i = 0;
        for(String property : beanProperties) {
            String setMethodName = "set" + property.substring(0, 1).toUpperCase() + 
                property.substring(1);
            try {
                setMethods[i] = clazz.getDeclaredMethod(setMethodName, propertyTypes[i++]);
            } catch (SecurityException se) {
                throw new Exception("屬性" + property + "對應的setter方法可能無法訪問.", se);
            } catch (NoSuchMethodException e) {
                String paramType = propertyTypes[--i].getName();
                throw new Exception("屬性" + property + "沒有參數類型爲" + paramType + 
                    "的setter方法.", e);
            }
        }
        return parseExcel(inputStream, setMethods, propertyTypes, clazz);
    }
    
    /**
     * 具體解析方法, 由公共方法準備好數據後調用, 因此不公開.
     * @param inputStream 準備解析的Excel文件對應的輸入流
     * @param setMethods setter方法數組, 與給定列順序一樣
     * @param propertyTypes JavaBean屬性類型數組
     * @param cls JavaBean類的Class對象
     * @return 解析成功後JavaBean集合
     * @throws Exception
     */
    private static <T> List<T> parseExcel(InputStream inputStream, Method [] setterMethods, 
            Class<?> [] propertyTypes, Class<T> beanType) throws Exception {
        Workbook workbook = Workbook.getWorkbook(inputStream);
        Sheet sheet = workbook.getSheet(0);
 
        List<T> parseResultList = new ArrayList<T>(sheet.getRows() - 1);
        for(int row = 1; row < sheet.getRows(); row++) {    // 跳過第0行, 一般第0行是列名
            T instance = beanType.newInstance();    // 創建實例
            Cell [] cells = sheet.getRow(row);
            for(Cell cell : cells) {
                if (cell.getColumn() >= setterMethods.length) break;
                Method setMethod = setterMethods[cell.getColumn()];
                Class<?> type = propertyTypes[cell.getColumn()];
                if (null != setMethod) {
                    // 調用新創建實例的setter
                    invokeSetterMethod(setMethod, instance, cell, type);
                }
            }
            parseResultList.add(instance);
        }
        workbook.close();
        return parseResultList;
    }
    
    /**
     * 取出Excel單元格中的數據並作相應轉換, 然後調用實例的設置屬性值方法(setters), 給實例設置指定值
     * @param setterMethod setter方法
     * @param o setterMethod調用所針對的實例
     * @param cell Excel單元格
     * @param paramType setter方法所需參數的類型
     * @throws Exception
     */
    private static void invokeSetterMethod(Method setterMethod, Object o, Cell cell,
            Class<?> paramType) throws Exception  {
        try {
            if (cell instanceof EmptyCell) {    // 單元格沒有內容, 將實體類相應屬性設置爲null
                setterMethod.invoke(o, new Object [] { null });
            } else if (String.class.isAssignableFrom(paramType) || char.class.isAssignableFrom(paramType)
                    || Character.class.isAssignableFrom(paramType)) {
                // String|char|Character設置爲String
                setterMethod.invoke(o, cell.getContents());
            } else if (Double.class.isAssignableFrom(paramType) || double.class.isAssignableFrom(paramType)) {    // Double|double
                Double number = ((NumberCell) cell).getValue();
                setterMethod.invoke(o, number);
            } else if (Integer.class.isAssignableFrom(paramType) || int.class.isAssignableFrom(paramType)) {        // Integer|int
                Double number = ((NumberCell) cell).getValue();
                setterMethod.invoke(o, number.intValue());
            } else if (Long.class.isAssignableFrom(paramType) || long.class.isAssignableFrom(paramType)) {        // Long|long
                Double number = ((NumberCell) cell).getValue();
                setterMethod.invoke(o, number.longValue());
            } else if (Float.class.isAssignableFrom(paramType) || float.class.isAssignableFrom(paramType)) {        // Float|float
                Double number = ((NumberCell) cell).getValue();
                setterMethod.invoke(o, number.floatValue());
            } else if (Short.class.isAssignableFrom(paramType) || short.class.isAssignableFrom(paramType)) {        // Short|short
                Double number = ((NumberCell) cell).getValue();
                setterMethod.invoke(o, number.shortValue());
            } else if (Boolean.class.isAssignableFrom(paramType) || boolean.class.isAssignableFrom(paramType)) {    // Boolean|boolean
                setterMethod.invoke(o, ((BooleanCell)cell).getValue());
            } else if (Date.class.isAssignableFrom(paramType)) {    // java.util.Date
                setterMethod.invoke(o, ((DateCell)cell).getDate());
            } else {
                throw new Exception("方法" + setterMethod.getName() + 
                    "所需要的參數類型不被支持.");
            }
        } catch (IllegalArgumentException e) {
            throw new Exception("setter方法" + setterMethod.getName() + "需要參數的類型爲" + 
                    setterMethod.getParameterTypes()[0].getName() + 
                    ", 傳入的類型爲" + paramType.getName(), e);
        }
    }
}


用Java時間也不短了,但對其一些高級特性總是不太明白,這其中就包括反射和泛型;也許有人覺得泛型不算高級特性,因爲我們經常會接觸到它,但我們真的理解它了嗎?看《Thinking In Java》的時候略有所悟,但放下書本後又覺得還是不明白,也許經常寫一些類似這種的通用代碼,對它們的理解會更深一點兒...

轉載自https://blog.csdn.net/zhyh1986/article/details/8511186

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