项目中经常需要导出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;
}
}