1. 程式人生 > 其它 >基於反射的excel對映外掛(直接把list集合中的物件對映成excel表)

基於反射的excel對映外掛(直接把list集合中的物件對映成excel表)

技術標籤:Java框架後端外掛javaexcelpoi

專案中經常需要匯出excel的表格,但是基本上都是呼叫第三方的外掛來進行一個直接呼叫。我們能不能通過反射寫一個通用的excel的外掛呢?

1. 依賴引入:

<!-- 處理Excel相關-->  
<dependency>  
    <groupId>org.apache.poi</groupId>  
    <artifactId>poi</artifactId>  
    <version>3.17</version>  
</dependency>

2. 方法引數的思考。

我們能不能定義一種規範,只有符合這種規範的實體類才能被對映成excel表?
解決方法:自定義介面,只有實現這個介面的類才能被excel化。參考了serializable的空實現,我們這裡可以直接空實現,到時候需要公共的方法的時候,可以方便的進行擴充套件。
介面程式碼:

/**  
 * @author: TMingYi
 * @date: 2020/12/12 19:50 */// 所有需要excel序列化的類,都必須實現這個介面。  
public interface Excelable extends Serializable {  
}

3. 介面定義

/**  
 * @param headMsg excel表頭的資訊。  
  * @param targetClass 目標類的class物件。  
  * @param list 目標類集合()  
  */  
public static void getExcelByObjectList(  
        String headMsg,  
        Class<? extends Excelable> targetClass,  
        List<? extends Excelable > list,  
        File file,  
        Map<String,String> map){  
	 // 具體的程式碼 
}
  • headMsg:excel的標題(第一行的描述文字),由外部傳入。
  • targetClass:目標類的Class物件,方便我們反射獲取值。
  • list:目標物件的集合。
  • file:具體生成的檔案。
  • map:類裡面的欄位名(駝峰)和中文意思的對應。由外部定義。

4. 如何根據欄位長度合併單元格?

我們第一行為描述資訊,需要根據現有的欄位的個數來合併單元格,那麼久必須知道目前物件的欄位有多少個?反射就可以幫我們完成。

// 獲取欄位的長度
Field[] fields = targetClass.getDeclaredFields();  
int fieldLength = fields.length;
// 建立excel的程式碼(略);
// 我們根據欄位的長度來設定我們的合併單元格長度。  
sheet.addMergedRegion(new CellRangeAddress(0,0,0,fieldLength - 1));

5. 欄位名對映成表頭

通過反射獲取欄位的Name,然後在map中尋找即可。

for (int i = 0 ; i < fieldLength ; i++){  
    // 使用field欄位的值來初始化我們的表格頭資訊;  
  HSSFCell cell = row2.createCell(i);  
    // 根據Map設定我們的頭頂的值。  
  cell.setCellValue(  
            map.get(fields[i].getName()) == null ?  
                    fields[i].getName() :  
                    map.get(fields[i].getName()));  
    cell.setCellStyle(innerStyle);  
    sheet.setColumnWidth(i,5000);  
}

6. 如何遍歷集合?

通過迴圈,每遍歷一行就建立一個HSSFRow,這裡會呼叫欄位的tostring方法進行填充單元格。也可以在抽象介面中定義特定的方法進行返回值,我這裡做了一個處理,如果發現欄位是Date的,就呼叫SimpleDateFormat進行一個格式化的操作。

// 填充我們實際的值!  
  for (int i = 0 ; i < list.size() ; i++){  
            HSSFRow tempRow = sheet.createRow(i + 2);  
  
            for (int j = 0 ; j < fieldLength ; j++){  
                // 使用field欄位的值來初始化我們的表格頭資訊;  
             try {  
                    // 把所有欄位都設定成可讀的。  
                    fields[j].setAccessible(true);  
                    HSSFCell cell = tempRow.createCell(j);  
                  // 對日期型別做一個特殊的處理。  
                   if ((fields[j].get(list.get(i))) instanceof Date){  
                        // 對日期型別進行格式化;  
  cell.setCellValue(SIMPLE_DATE_FORMAT.format((Date)(fields[j].get(list.get(i)))));  
                    }else {  
                        cell.setCellValue(fields[j].get(list.get(i)).toString());  
                    }  
                    cell.setCellStyle(innerStyle);  
                } catch (IllegalAccessException e) {  
                    e.printStackTrace();  
                    throw new RuntimeException( "fields value not toString!");  
                }  
            }  
        }

至於設定單元格樣式,可以參考文章最後的完整程式碼。
最終效果(標題的時間是在程式碼裡面加的):
在這裡插入圖片描述

創造了excel之後,可以通過HTTP 協議直接返回給瀏覽器下載:

/**  
 * @param trainingId 實訓資訊的id;  
 * @param response 響應體包含excel物件。  
  */  
@GetMapping("get/studentTable/{trainingId}")  
@RequireRole(RoleConst.ROLE_TYPE_ADMIN)  
public void getStudentTable(HttpServletResponse response) {  
    // 獲得list程式碼(略)
    // 設定響應頭,預設下載。  
  response.setHeader("Content-disposition", "attachment;filename=666.xls");//預設Excel名稱  
  try {  
        ExcelUtil.getExcelByObjectList(  
                "攀枝花學院數學與計算機學院實訓名單" ,  
                StudentInfoVO.class,  
                resuls,response.getOutputStream(),  
                StudentConst.STUDENT_MAP);  
        response.flushBuffer();  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
}

完整程式碼:

package sjjx.manage.util;  
  
import org.apache.ibatis.annotations.Param;  
import org.apache.poi.hssf.usermodel.*;  
import org.apache.poi.ss.usermodel.HorizontalAlignment;  
import org.apache.poi.ss.util.CellRangeAddress;  
import sjjx.manage.config.mypinterface.Excelable;  
  
import java.io.File;  
import java.io.IOException;  
import java.io.OutputStream;  
import java.lang.reflect.Field;  
import java.text.SimpleDateFormat;  
import java.util.Arrays;  
import java.util.Date;  
import java.util.List;  
import java.util.Map;  
import java.util.concurrent.Future;  
  
/**  
 * @author TMingYi * @classname ExcelUtil * @description 處理Excel相關的方法;  
 * @date 2020/12/12 19:36 */public class ExcelUtil {  
  
    private static final String START_CHAR = "(";  
  
    private static final String END_CHAR = ")";  
  
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");  
  
  
    /**  
 * @param headMsg excel表頭的資訊。  
  * @param targetClass 目標類的class物件。  
  * @param list 目標類集合()  
  * @return 包含目標物件的 Futrue;  
 */  public static void getExcelByObjectList(  
            String headMsg,  
            Class<? extends Excelable> targetClass,  
            List<? extends Excelable > list,  
            OutputStream stream,  
            Map<String, String> map){  
        // 獲得表單物件。  
  HSSFWorkbook wk = getHSSFWorkBook(headMsg,targetClass,list, map);  
  
        try {  
            wk.write(stream);  
        } catch (IOException e) {  
            e.printStackTrace();  
            throw new RuntimeException( "transform stream fail");  
        }  
    }  
  
  
    /**  
 * @param headMsg excel表頭的資訊。  
  * @param targetClass 目標類的class物件。  
  * @param list 目標類集合()  
  */  
  public static void getExcelByObjectList(  
            String headMsg,  
            Class<? extends Excelable> targetClass,  
            List<? extends Excelable > list,  
            File file,  
            Map<String,String> map){  
        HSSFWorkbook wk = getHSSFWorkBook(headMsg,targetClass,list,map);  
        try {  
            wk.write(file);  
        } catch (IOException e) {  
            e.printStackTrace();  
            throw new RuntimeException( "transform stream file");  
        }  
    }  
  
    private static HSSFWorkbook getHSSFWorkBook(  
            String headMsg, Class<? extends Excelable> targetClass,  
            List<? extends Excelable> list, Map<String, String> map) {  
        // 獲取所有欄位的值。  
  Field[] fields = targetClass.getDeclaredFields();  
        int fieldLength = fields.length;  
        headMsg = wraperTime(headMsg);  
  
        // 建立一個excel;  
  HSSFWorkbook wk = new HSSFWorkbook();  
  
        // 建立一個工作表;  
  HSSFSheet sheet = wk.createSheet("資訊表");  
  
        // 建立第一行;  
  HSSFRow row1 = sheet.createRow(0);  
        row1.setHeight((short) 500);  
  
        HSSFCell cell1 = row1.createCell(0);  
        cell1.setCellValue(headMsg);  
  
        // 設定頭部字型的樣式  
  HSSFCellStyle headStyle = wk.createCellStyle();  
        HSSFFont headFont = wk.createFont();  
        headFont.setBold(true);  
        headFont.setFontHeightInPoints((short) 16);  
        headStyle.setFont(headFont);  
        headStyle.setAlignment(HorizontalAlignment.CENTER);  
        cell1.setCellStyle(headStyle);  
        // 設定樣式結束。  
  // 進行單元格的合併  
  // 我們根據欄位的長度來設定我們的合併單元格長度。  
  sheet.addMergedRegion(new CellRangeAddress(0,0,0,fieldLength - 1));  
  
        // 設定表格裡面資料的樣式!  
  HSSFCellStyle innerStyle = wk.createCellStyle();  
        HSSFFont innerFont = wk.createFont();  
        innerFont.setFontHeightInPoints((short) 12);  
        innerStyle.setFont(innerFont);  
        innerStyle.setAlignment(HorizontalAlignment.CENTER);  
  
        // 建立第二行,用於放置頭資訊。  
  HSSFRow row2 = sheet.createRow(1);  
  
        for (int i = 0 ; i < fieldLength ; i++){  
            // 使用field欄位的值來初始化我們的表格頭資訊;  
  HSSFCell cell = row2.createCell(i);  
            // 根據Map設定我們的頭頂的值。  
  cell.setCellValue(  
                    map.get(fields[i].getName()) == null ?  
                            fields[i].getName() :  
                            map.get(fields[i].getName()));  
            cell.setCellStyle(innerStyle);  
            sheet.setColumnWidth(i,5000);  
        }  
  
  
        // 填充我們實際的值!  
  for (int i = 0 ; i < list.size() ; i++){  
            HSSFRow tempRow = sheet.createRow(i + 2);  
  
            for (int j = 0 ; j < fieldLength ; j++){  
                // 使用field欄位的值來初始化我們的表格頭資訊;  
  try {  
                    // 把所有欄位都設定成可讀的。  
  fields[j].setAccessible(true);  
                    HSSFCell cell = tempRow.createCell(j);  
// 對日期型別做一個特殊的處理。  
  if ((fields[j].get(list.get(i))) instanceof Date){  
                        // 對日期型別進行格式化;  
  cell.setCellValue(SIMPLE_DATE_FORMAT.format((Date)(fields[j].get(list.get(i)))));  
                    }else {  
                        cell.setCellValue(fields[j].get(list.get(i)).toString());  
                    }  
                    cell.setCellStyle(innerStyle);  
                } catch (IllegalAccessException e) {  
                    e.printStackTrace();  
                    throw new RuntimeException( "fields value not toString!");  
                }  
            }  
        }  
        return wk;  
    }  
  
    // 給我們的頭部包裝一個時間戳  
  private static String wraperTime(String headMsg) {  
        String nowTime = DateUtil.getTimeNow("yyyy-MM-dd");  
        return headMsg + START_CHAR + nowTime + END_CHAR;  
    }  
}