基於反射的excel對映外掛(直接把list集合中的物件對映成excel表)
阿新 • • 發佈:2020-12-22
專案中經常需要匯出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;
}
}