EasyExcel教程
本文使用的技術是Alibaba集團開源的EasyExcel技術,該技術是針對Apache POI技術的封裝和優化,主要解決了POI技術的耗記憶體問題,并且提供了較好的API使用。
- 使用步驟繁瑣
- 動态寫出Excel操作非常麻煩
- 對于新手來說,很難在短時間内上手
- 讀寫時需要占用較大的内容,當資料量大時容器發生OOM
Maven依賴
<!-- easyexcel 依賴 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.7</version>
</dependency>
EasyExcel API分析介紹
- Ø EasyExcel入口類,用于建構開始各種操作
- Ø ExcelReaderBuilder ExcelWriterBuilder 建構出一個 ReadWorkbook WriteWorkbook,可以了解成一個excel對象,一個excel隻要建構一個
- Ø ExcelReaderSheetBuilder ExcelWriterSheetBuilder 建構出一個 ReadSheet WriteSheet對象,可以了解成excel裡面的一頁,每一頁都要建構一個
- Ø ReadListener在每一行讀取完畢後都會調用ReadListener來處理資料
- Ø WriteHandler在每一個操作包括建立單元格、建立表格等都會調用WriteHandler來處理資料
- Ø 所有配置都是繼承的,Workbook的配置會被Sheet繼承,是以在用EasyExcel設定參數的時候,在EasyExcel…sheet()方法之前作用域是整個sheet,之後針對單個sheet
EasyExcel 注解
- Ø ExcelProperty 指定目前字段對應excel中的那一列。可以根據名字或者Index去比對。當然也可以不寫,預設第一個字段就是index=0,以此類推。千萬注意,要麼全部不寫,要麼全部用index,要麼全部用名字去比對。千萬别三個混着用,除非你非常了解源代碼中三個混着用怎麼去排序的。
- Ø ExcelIgnore 預設所有字段都會和excel去比對,加了這個注解會忽略該字段
- Ø DateTimeFormat 日期轉換,用String去接收excel日期格式的資料會調用這個注解。裡面的value參照java.text.SimpleDateFormat
- Ø NumberFormat 數字轉換,用String去接收excel數字格式的資料會調用這個注解。裡面的value參照java.text.DecimalFormat
- Ø ExcelIgnoreUnannotated 預設不加ExcelProperty 的注解的都會參與讀寫,加了不會參與
通用參數
- Ø ReadWorkbook,ReadSheet 都會有的參數,如果為空,預設使用上級。
- Ø converter 轉換器,預設加載了很多轉換器。也可以自定義。
- Ø readListener 監聽器,在讀取資料的過程中會不斷的調用監聽器。
- Ø headRowNumber 需要讀的表格有幾行頭資料。預設有一行頭,也就是認為第二行開始起為資料。
- Ø head 與clazz二選一。讀取檔案頭對應的清單,會根據清單比對資料,建議使用class。
- Ø clazz 與head二選一。讀取檔案的頭對應的class,也可以使用注解。如果兩個都不指定,則會讀取全部資料。
- Ø autoTrim 字元串、表頭等資料自動trim
- Ø password 讀的時候是否需要使用密碼
ReadWorkbook(了解成excel對象)參數
- Ø excelType 目前excel的類型 預設會自動判斷
- Ø inputStream 與file二選一。讀取檔案的流,如果接收到的是流就隻用,不用流建議使用file參數。因為使用了inputStream easyexcel會幫忙建立臨時檔案,最終還是file
- Ø file 與inputStream二選一。讀取檔案的檔案。
- Ø autoCloseStream 自動關閉流。
- Ø readCache 預設小于5M用 記憶體,超過5M會使用 EhCache,這裡不建議使用這個參數。
ReadSheet(就是excel的一個Sheet)參數
- Ø sheetNo 需要讀取Sheet的編碼,建議使用這個來指定讀取哪個Sheet
- Ø sheetName 根據名字去比對Sheet,excel 2003不支援根據名字去比對
注解
- Ø ExcelProperty index 指定寫到第幾列,預設根據成員變量排序。value指定寫入的名稱,預設成員變量的名字,多個value可以參照快速開始中的複雜頭
- Ø ExcelIgnore 預設所有字段都會寫入excel,這個注解會忽略這個字段
- Ø DateTimeFormat 日期轉換,将Date寫到excel會調用這個注解。裡面的value參照java.text.SimpleDateFormat
- Ø NumberFormat 數字轉換,用Number寫excel會調用這個注解。裡面的value參照java.text.DecimalFormat
參數
- Ø WriteWorkbook,WriteSheet ,WriteTable都會有的參數,如果為空,預設使用上級。
- Ø writeHandler 寫的處理器。可以實作WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在寫入excel的不同階段會調用
- Ø relativeHeadRowIndex 距離多少行後開始。也就是開頭空幾行
- Ø needHead 是否導出頭
- Ø head 與clazz二選一。寫入檔案的頭清單,建議使用class。
- Ø clazz 與head二選一。寫入檔案的頭對應的class,也可以使用注解。
WriteWorkbook(了解成excel對象)參數
- Ø excelType 目前excel的類型 預設xlsx
- Ø outputStream 與file二選一。寫入檔案的流
- Ø file 與outputStream二選一。寫入的檔案
- Ø templateInputStream 模闆的檔案流
- Ø templateFile 模闆檔案
- Ø password 寫的時候是否需要使用密碼
- Ø useDefaultStyle 寫的時候是否是使用預設頭
WriteSheet(就是excel的一個Sheet)參數
- Ø sheetNo 需要寫入的編碼。預設0
- Ø sheetName 需要些的Sheet名稱,預設同sheetNo
WriteTable(就把excel的一個Sheet,一塊區域看一個table)參數
- Ø tableNo 需要寫入的編碼。預設0
EasyExcel用法指南
簡單的讀取excel檔案
public void read() {
String fileName = "demo.xlsx";
// 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
// 參數一:讀取的excel檔案路徑
// 參數二:讀取sheet的一行,将參數封裝在DemoData實體類中
// 參數三:讀取每一行的時候會執行DemoDataListener監聽器
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
簡單的寫入excel檔案
@Test
public void simpleWrite() {
String fileName = "demo.xlsx";
// 這裡 需要指定寫用哪個class去讀,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
// 如果這裡想使用03 則 傳入excelType參數即可
// 參數一:寫入excel檔案路徑
// 參數二:寫入的資料類型是DemoData
// data()方法是寫入的資料,結果是List<DemoData>集合
EasyExcel.write(fileName, DemoData.class).sheet("模闆").doWrite(data());
}
Web上傳與下載下傳
/**
excel檔案的下載下傳
*/
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=demo.xlsx");
EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模闆").doWrite(data());
}
/**
excel檔案的上傳
*/
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), DemoData.class, new DemoDataListener()).sheet().doRead();
return "success";
}
詳解讀取Excel
對象模型
// 如果沒有特殊說明,下面的案例将預設使用這個實體類
public class DemoData {
/**
* 強制讀取第三個 這裡不建議 index 和 name 同時用,要麼一個對象隻用index,要麼一個對象隻用name去比對
*/
@ExcelProperty(index = 2)
//我想接收百分比的數字
@NumberFormat("#.##%")
@ExcelProperty(value="浮點數标題", converter = CustomStringConverter.class)
private Double doubleData;
/**
* 用名字去比對,這裡需要注意,如果名字重複,會導緻隻有一個字段讀取到資料
*/
@ExcelProperty(value="字元串标題", converter = CustomStringConverter.class)
// converter屬性定義自己的字元串轉換器
private String string;
@DateTimeFormat("yyyy年MM月dd日 HH時mm分ss秒")
@ExcelProperty("日期标題")
//這裡用string 去接日期才能格式化
private Date date;
}
監聽器
// 如果沒有特殊說明,下面的案例将預設使用這個監聽器
public class DemoDataListener extends AnalysisEventListener<DemoData> {
List<DemoData> list = new ArrayList<DemoData>();
/**
* 如果使用了spring,請使用這個構造方法。每次建立Listener的時候需要把spring管理的類傳進來
*/
public DemoDataListener() {}
/**
* 這個每一條資料解析都會來調用
*
* @param data
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
System.out.println("解析到一條資料:{}", JSON.toJSONString(data));
list.add(data);
}
/**
* 所有資料解析完成了 都會來調用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println(JSON.toJSONString(list));
}
}
代碼
@Test
public void simpleRead() {
// 寫法1:
String fileName = "demo.xlsx";
// 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
// 寫法2:
fileName = "demo.xlsx";
ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
// 這裡千萬别忘記關閉,讀的時候會建立臨時檔案,到時磁盤會崩的
excelReader.finish();
}
讀取多個sheet
@Test
public void repeatedRead() {
String fileName = "demo.xlsx";
// 讀取全部sheet
// 這裡需要注意 DemoDataListener的doAfterAllAnalysed 會在每個sheet讀取完畢後調用一次。然後所有sheet都會往同一個DemoDataListener裡面寫
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();
// 讀取部分sheet
fileName = "demo.xlsx";
ExcelReader excelReader = EasyExcel.read(fileName).build();
// 這裡為了簡單 是以注冊了 同樣的head 和Listener 自己使用功能必須不同的Listener
// readSheet參數設定讀取sheet的序号
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
// 這裡注意 一定要把sheet1 sheet2 一起傳進去,不然有個問題就是03版的excel 會讀取多次,浪費性能
excelReader.read(readSheet1, readSheet2);
// 這裡千萬别忘記關閉,讀的時候會建立臨時檔案,到時磁盤會崩的
excelReader.finish();
}
自定義格式轉換
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 這裡讀的時候會調用
*
* @param cellData
* NotNull
* @param contentProperty
* Nullable
* @param globalConfiguration
* NotNull
* @return
*/
@Override
public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return "自定義:" + cellData.getStringValue();
}
/**
* 這裡是寫的時候會調用 不用管
*
* @param value
* NotNull
* @param contentProperty
* Nullable
* @param globalConfiguration
* NotNull
* @return
*/
@Override
public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new CellData(value);
}
}
多行頭
@Test
public void complexHeaderRead() {
String fileName = "demo.xlsx";
// 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet()
// 這裡可以設定1,因為頭就是一行。如果多行頭,可以設定其他值。不傳入預設1行
.headRowNumber(1).doRead();
}
讀取表頭資料
覆寫監聽器invokeHeadMap方法
/**
* 這裡會一行行的傳回頭
* 監聽器隻需要重寫這個方法就可以讀取到頭資訊
* @param headMap
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
LOGGER.info("解析到一條頭資料:{}", JSON.toJSONString(headMap));
}
異常處理
覆寫監聽器onException方法
/**
* 監聽器實作這個方法就可以在讀取資料的時候擷取到異常資訊
*/
@Override
public void onException(Exception exception, AnalysisContext context) {
LOGGER.error("解析失敗,但是繼續解析下一行:{}", exception.getMessage());
// 如果是某一個單元格的轉換異常 能擷取到具體行号
// 如果要擷取頭的資訊 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
LOGGER.error("第{}行,第{}列解析異常", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex());
}
}
導出指定的列
@Test
public void excludeOrIncludeWrite() {
String fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
// 忽略 date 不導出
Set<String> excludeColumnFiledNames = new HashSet<String>();
excludeColumnFiledNames.add("date");
// 這裡 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("忽略date")
.doWrite(data());
fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
// 根據使用者傳入字段 假設我們隻要導出 date
Set<String> includeColumnFiledNames = new HashSet<String>();
includeColumnFiledNames.add("date");
// 這裡 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("導出date")
.doWrite(data());
}
調整指定列順序
public class IndexData {
/**
* 導出的excel第二列和第四列将空置
*/
@ExcelProperty(value = "字元串标題", index = 0)
private String string;
@ExcelProperty(value = "日期标題", index = 2)
private Date date;
@ExcelProperty(value = "浮點數标題", index = 4)
private Double doubleData;
}
複雜頭寫入
public class ComplexHeadData {
/**
* 主标題 将整合為一個單元格效果如下:
* —————————————————————————
* | 主标題 |
* —————————————————————————
* |字元串标題|日期标題|數字标題|
* —————————————————————————
*/
@ExcelProperty({"主标題", "字元串标題"})
private String string;
@ExcelProperty({"主标題", "日期标題"})
private Date date;
@ExcelProperty({"主标題", "數字标題"})
private Double doubleData;
}
前面屬于主标題,後面屬于副标題
圖檔導出
@Data
@ContentRowHeight(200)
@ColumnWidth(200 / 8)
public class ImageData {
// 圖檔導出方式有5種
private File file;
private InputStream inputStream;
/**
* 如果string類型 必須指定轉換器,string預設轉換成string,該轉換器是官方支援的
*/
@ExcelProperty(converter = StringImageConverter.class)
private String string;
private byte[] byteArray;
/**
* 根據url導出 版本2.1.1才支援該種模式
*/
private URL url;
}
@Test
public void imageWrite() throws Exception {
String fileName = "imageWrite" + System.currentTimeMillis() + ".xlsx";
// 如果使用流 記得關閉
InputStream inputStream = null;
try {
List<ImageData> list = new ArrayList<ImageData>();
ImageData imageData = new ImageData();
list.add(imageData);
String imagePath = "converter" + File.separator + "img.jpg";
// 放入五種類型的圖檔 根據實際使用隻要選一種即可
imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));
imageData.setFile(new File(imagePath));
imageData.setString(imagePath);
inputStream = FileUtils.openInputStream(new File(imagePath));
imageData.setInputStream(inputStream);
imageData.setUrl(new URL(
"https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg"));
EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list);
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
列寬、行高
@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
@ExcelProperty("字元串标題")
private String string;
@ExcelProperty("日期标題")
private Date date;
/**
* 寬度為50,覆寫上面的寬度25
*/
@ColumnWidth(50)
@ExcelProperty("數字标題")
private Double doubleData;
}
- @HeadRowHeight(value = 35) // 表頭行高
- @ContentRowHeight(value = 25) // 内容行高
- @ColumnWidth(value = 50) // 列寬
此外還有,自适應寬度,但是這個不是特别精确
@Test
void contextLoads() {
EasyExcel.write("自适應.xlsx", Student.class)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.sheet()
.doWrite(getData());
}
動态表頭
@Test
public void dynamicHeadWrite() {
String fileName = "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName)
// 這裡放入動态頭
.head(head()).sheet("模闆")
// 當然這裡資料也可以用 List<List<String>> 去傳入
.doWrite(data());
}
// 動态表頭的資料格式List<List<String>>
private List<List<String>> head() {
List<List<String>> list = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("字元串" + System.currentTimeMillis());
List<String> head1 = new ArrayList<String>();
head1.add("數字" + System.currentTimeMillis());
List<String> head2 = new ArrayList<String>();
head2.add("日期" + System.currentTimeMillis());
list.add(head0);
list.add(head1);
list.add(head2);
return list;
}
合并單元格
@Test
public void mergeWrite() {
String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("合并單元格")
.doWrite(data());
}
- 每隔2行會合并 把eachColumn 設定成 3 也就是我們資料的長度,是以就第一列會合并。當然其他合并政策也可以自己寫
- 這裡 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
web資料寫出
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 這裡URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關系
String fileName = URLEncoder.encode("資料寫出", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模闆").doWrite(data());
}
模闆格式導出
如果需要橫向填充隻需要模闆設定好就可以。
簡單的Excel模闆
public class FillData {
private String name;
private double number;
// getting setting
}
實作模闆填充
@Test
public void simpleFill() {
String templateFileName = "simple.xlsx";
// 方案1 根據對象填充
String fileName = System.currentTimeMillis() + ".xlsx";
// 這裡 會填充到第一個sheet, 然後檔案流會自動關閉
FillData fillData = new FillData();
fillData.setName("知春秋");
fillData.setNumber(25);
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);
// 方案2 根據Map填充
fileName = System.currentTimeMillis() + ".xlsx";
// 這裡 會填充到第一個sheet, 然後檔案流會自動關閉
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "知春秋");
map.put("number", 25);
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}
- 模闆注意 用{} 來表示你要用的變量 如果本來就有"{","}" 特殊字元 用"{","}"代替
複雜的填充
@Test
public void complexFill() {
String templateFileName = "complex.xlsx";
String fileName = System.currentTimeMillis() + ".xlsx";
ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 如果資料量大 list不是最後一行 參照下一個
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(data(), fillConfig, writeSheet);
excelWriter.fill(data(), fillConfig, writeSheet);
// 其他參數可以使用Map封裝
Map<String, Object> map = new HashMap<String, Object>();
excelWriter.fill(map, writeSheet);
excelWriter.finish();
}
- // 這裡注意 入參用了forceNewRow 代表在寫入list的時候不管list下面有沒有空行 都會建立一行,然後下面的資料往後移動。預設 是false,會直接使用下一行,如果沒有則建立。
- // forceNewRow 如果設定了true,有個缺點 就是他會把所有的資料都放到記憶體了,是以慎用
- // 簡單的說 如果你的模闆有list,且list不是最後一行,下面還有資料需要填充 就必須設定 forceNewRow=true 但是這個就會把所有資料放到記憶體 會很耗記憶體
總結一個工具類
public class ExcelUtil {
/**
* 寫出一個 excel 檔案到本地
* <br />
* 将類型所有加了 @ExcelProperty 注解的屬性全部寫出
*
* @param fileName 檔案名 不要字尾
* @param sheetName sheet名
* @param data 寫出的資料
* @param clazz 要寫出資料類的Class類型對象
* @param <T> 寫出的資料類型
*/
public static <T> void writeExcel(String fileName, String sheetName, List<T> data, Class<T> clazz) {
writeExcel(null, fileName, sheetName, data, clazz);
}
/**
* 按照指定的屬性名進行寫出 一個 excel
*
* @param attrName 指定的屬性名 必須與資料類型的屬性名一緻
* @param fileName 檔案名 不要字尾
* @param sheetName sheet名
* @param data 要寫出的資料
* @param clazz 要寫出資料類的Class類型對象
* @param <T> 要寫出的資料類型
*/
public static <T> void writeExcel(Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {
fileName = StringUtils.isBlank(fileName) ? "學生管理系統" : fileName;
sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;
try(FileOutputStream fos = new FileOutputStream(fileName)) {
write(fos,attrName,sheetName,data,clazz);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* 讀取 指定格式的 excel文檔
*
* @param fileName 檔案名
* @param clazz 資料類型的class對象
* @param <T> 資料類型
* @return
*/
public static <T> List<T> readExcel(String fileName, Class<T> clazz) {
return readExcel(fileName, clazz, null);
}
/**
* 取 指定格式的 excel文檔
* 注意一旦傳入自定義監聽器,則傳回的list為空,資料需要在自定義監聽器裡面擷取
*
* @param fileName 檔案名
* @param clazz 資料類型的class對象
* @param readListener 自定義監聽器
* @param <T> 資料類型
* @return
*/
public static <T> List<T> readExcel(String fileName, Class<T> clazz, ReadListener<T> readListener) {
try(FileInputStream fis = new FileInputStream(fileName)) {
return read(fis,clazz,readListener);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* 導出 一個 excel
* 導出excel所有資料
* @param response
* @param fileName 件名 最好為英文,不要字尾名
* @param sheetName sheet名
* @param data 要寫出的資料
* @param clazz 要寫出資料類的Class類型對象
* @param <T> 要寫出的資料類型
*/
public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> data, Class<T> clazz) {
export(response, null, fileName, sheetName, data, clazz);
}
/**
* 按照指定的屬性名進行寫出 一個 excel
*
* @param response
* @param attrName 指定的屬性名 必須與資料類型的屬性名一緻
* @param fileName 檔案名 最好為英文,不要字尾名
* @param sheetName sheet名
* @param data 要寫出的資料
* @param clazz 要寫出資料類的Class類型對象
* @param <T> 要寫出的資料類型
*/
public static <T> void export(HttpServletResponse response, Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {
fileName = StringUtils.isBlank(fileName) ? "student-system-manager" : fileName;
sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setCharacterEncoding("utf-8");
response.addHeader("Content-disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());
try(OutputStream os = response.getOutputStream()) {
write(os,attrName,sheetName,data,clazz);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 接收一個excel檔案,并且進行解析
* 注意一旦傳入自定義監聽器,則傳回的list為空,資料需要在自定義監聽器裡面擷取
* @param multipartFile excel檔案
* @param clazz 資料類型的class對象
* @param readListener 監聽器
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile multipartFile,Class<T> clazz,ReadListener<T> readListener) {
try(InputStream inputStream = multipartFile.getInputStream()) {
return read(inputStream,clazz,readListener);
} catch (IOException e) {
e.printStackTrace();
}
}
private static <T> void write(OutputStream os, Set<String> attrName, String sheetName, List<T> data, Class<T> clazz) {
ExcelWriterBuilder write = EasyExcel.write(os, clazz);
// 如果沒有指定要寫出那些屬性資料,則寫出全部
if (!CollectionUtils.isEmpty(attrName)) {
write.includeColumnFiledNames(attrName);
}
write.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(sheetName).doWrite(data);
}
private static <T> List<T> read(InputStream in,Class<T> clazz, ReadListener<T> readListener) {
List<T> list = new ArrayList<>();
Optional<ReadListener> optional = Optional.ofNullable(readListener);
EasyExcel.read(in, clazz, optional.orElse(new AnalysisEventListener<T>() {
@Override
public void invoke(T data, AnalysisContext context) {
list.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("解析完成");
}
})).sheet().doRead();
return list;
}
}
參考資料
- https://github.com/alibaba/easyexcel/blob/master/docs/API.md 官方api
- https://github.com/alibaba/easyexcel easyexcel github 位址
- https://blog.csdn.net/sinat_32366329/article/details/103109058 easyexcel總結
- https://alibaba-easyexcel.github.io/index.html 官方示例