web工程中需要大量的excel导出功能,但是每个导出功能都直接调用POI原生代码,会导致代码量的冗余,大量和业务无关的代码和业务代码混杂在一起,稀释了业务代码,降低代码的可读性。同时导出excel通用的部分很多,单独书写也降低了代码的复用性。
于是我就从网上找到了一个excel导出工具类NewExportToExcelUtil.java,可以支持单个sheet的excel导出,只要传入对象数组即可。但是我在使用过程中,很多需求不能够满足,于是进行了四项扩展,使得他更加强大。
1.兼容Map:
数组内容之前只能从从Object的属性和值的解析,但是我们很多项目数组里存的是Map,于是我扩展了对Map键值对的解析,用Map的键相当于object的属性,Map的值相当于object的值,于是实现了和前面方法的无缝对接,从而实现了对Map支持的扩展。
方法就是在解析之前,对Object的类进行判断,如果是Map就当Map解析,否则执行之前的解析方法。代码如下:
2.从单sheet到多sheet:
之前传参的数据是数组对象,扩展为多sheet,需要把数组再组合成一个更大的数组。于是传参格式之上又封装了一层数组。原来生成单个sheet的代码之外包装了一层循环,便实现了多sheet的支持。代码如下:
3.合并列的支持:
有些需求里,需要把同列相同的元素合并起来,有的不需要,我就使用一个布尔值的参数来控制合并的开关。当合并开关打开时,我们在生成完sheet之后再进行合并处理,为了找出相同内容的列,我们先把sheet里的数据存入一个比对数组。然后循环遍历比对数组,对符合要求的列进行poi的列合并操作,代码如下:
4.增加隐藏列的支持:
有些需要excel导入的表格里需要存储一些ID信息作为列的唯一标示,但是这些列需要隐藏起来,以免用户手动修改破坏了这些值。我没有通过增加参数来实现,因为参数太多增加了工具的复杂性。于是我们对列的名称下想办法,当列的名称以hide_开头时,该列隐藏。虽然hide_开头的表头不好看,但是隐藏起来就看不到了,这个缺点是发现不了的,可以忽略掉。(新特性3:sheet是否需要合并时我也想到过这个方案,但是sheet的值用户是可以看到的,加上特定的格式会在文件里被看出来,于是选择了扩展参数的方案)当生成完sheet时进行列隐藏操作,遍历所有的列表,当列名符合hide_开头的条件时,执行POI的列隐藏操作。代码如下:
增加这四个特性后,excel导出需要的大多数需求基本上都能通过这四个特性实现了。
源代码如下:
package com.ibaby.framework.utils;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.System.out;
/**
* 导出Excel
* @author Guang
*
*/
public class NewExportToExcelUtil {
private static final Logger logger =LoggerFactory.getLogger(NewExportToExcelUtil.class);
/**
* 导出Excel
* @param excelName 要导出的excel名称
* @param list 要导出的数据集合
* @param fieldMap 中英文字段对应Map,即要导出的excel表头
* @param response 使用response可以导出到浏览器
* @return
*/
public static <T> void export(String excelName,String[] sheets,List<T>[] list,LinkedHashMap<String, String>[] fieldMap,boolean[] merges,HttpServletResponse response){
// 设置默认文件名为当前时间:年月日时分秒
if (excelName==null || excelName=="") {
excelName = new SimpleDateFormat("yyyyMMddhhmmss").format(
new Date()).toString();
}
// 设置response头信息
response.reset();
response.setContentType("application/vnd.ms-excel"); // 改成输出excel文件
try {
response.setHeader("Content-disposition", "attachment; filename="
+new String(excelName.getBytes("gb2312"), "ISO-8859-1") + ".xls");
} catch (UnsupportedEncodingException e1) {
logger.info(e1.getMessage());
}
try {
//创建一个WorkBook,对应一个Excel文件
HSSFWorkbook wb=new HSSFWorkbook();
//创建单元格,并设置值表头 设置表头居中
HSSFCellStyle style=wb.createCellStyle();
//创建一个居中格式
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
HSSFFont font = wb.createFont();
font.setFontName("宋体");
font.setFontHeightInPoints((short) 12);// 设置字体大小
style.setFont(font);
// 指定当单元格内容显示不下时自动换行
style.setWrapText(true);
for(int i=0;i<sheets.length;i++){
//在Workbook中,创建一个sheet,对应Excel中的工作薄(sheet)
HSSFSheet sheet=wb.createSheet(sheets[i]);
// 填充工作表
fillSheet(sheet,list[i],fieldMap[i],style,merges[i]);
//隐藏列
int index = 0; //列数
for (Entry<String, String> entry : fieldMap[i].entrySet()) {
String value = entry.getValue();
if(value != null && value.indexOf("hide_")==0){
sheet.setColumnHidden(index,true);
}
index++;
}
}
//将文件输出
OutputStream ouputStream = response.getOutputStream();
wb.write(ouputStream);
ouputStream.flush();
ouputStream.close();
} catch (Exception e) {
logger.info("导出Excel失败!");
logger.error(e.getMessage());
}
}
/**
* 导出Excel到本地
* @param excelName 要导出的excel名称
* @param list 要导出的数据集合
* @param fieldMap 中英文字段对应Map,即要导出的excel表头
* @param response 使用response可以导出到浏览器
* @param response path 本地路径
* @return
*/
public static <T> void exportTopath(String excelName,String[] sheets,List<T>[] list,LinkedHashMap<String, String>[] fieldMap,boolean[] merges,HttpServletResponse response,String realPath){
// 设置默认文件名为当前时间:年月日时分秒
if (excelName==null || excelName=="") {
excelName = new SimpleDateFormat("yyyyMMddhhmmss").format(
new Date()).toString();
}
// 设置response头信息
/* response.reset();
response.setContentType("application/vnd.ms-excel"); // 改成输出excel文件*/
/* try {
response.setHeader("Content-disposition", "attachment; filename="
+new String(excelName.getBytes("gb2312"), "ISO-8859-1") + ".xls");
} catch (UnsupportedEncodingException e1) {
logger.info(e1.getMessage());
}*/
try {
//创建一个WorkBook,对应一个Excel文件
HSSFWorkbook wb=new HSSFWorkbook();
//创建单元格,并设置值表头 设置表头居中
HSSFCellStyle style=wb.createCellStyle();
//创建一个居中格式
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
HSSFFont font = wb.createFont();
font.setFontName("宋体");
font.setFontHeightInPoints((short) 12);// 设置字体大小
style.setFont(font);
// 指定当单元格内容显示不下时自动换行
style.setWrapText(true);
for(int i=0;i<sheets.length;i++){
//在Workbook中,创建一个sheet,对应Excel中的工作薄(sheet)
HSSFSheet sheet=wb.createSheet(sheets[i]);
// 填充工作表
fillSheet(sheet,list[i],fieldMap[i],style,merges[i]);
//隐藏列
int index = 0; //列数
for (Entry<String, String> entry : fieldMap[i].entrySet()) {
String value = entry.getValue();
if(value != null && value.indexOf("hide_")==0){
sheet.setColumnHidden(index,true);
}
index++;
}
}
//将文件输出
/* OutputStream ouputStream = response.getOutputStream();
wb.write(ouputStream);*/
// 填写有误
FileOutputStream out = null;
out = new FileOutputStream(realPath);
wb.write(out);
out.flush();
out.close();
} catch (Exception e) {
logger.info("导出Excel失败!");
logger.error(e.getMessage());
}
}
/**
* Remove a row by its index
* @param sheet a Excel sheet
* @param rowIndex a 0 based index of removing row
*/
public static void removeRow(Sheet sheet, int rowIndex) {
int lastRowNum=sheet.getLastRowNum();
if(rowIndex>=0&&rowIndex<lastRowNum)
sheet.shiftRows(rowIndex+1,lastRowNum,-1);//将行号为rowIndex+1一直到行号为lastRowNum的单元格全部上移一行,以便删除rowIndex行
if(rowIndex==lastRowNum){
Row removingRow=sheet.getRow(rowIndex);
if(removingRow!=null)
sheet.removeRow(removingRow);
}
}
/**
* 根据字段名获取字段对象
*
* @param fieldName
* 字段名
* @param clazz
* 包含该字段的类
* @return 字段
*/
public static Field getFieldByName(String fieldName, Class<?> clazz) {
// 拿到本类的所有字段
Field[] selfFields = clazz.getDeclaredFields();
// 如果本类中存在该字段,则返回
for (Field field : selfFields) {
//如果本类中存在该字段,则返回
if (field.getName().equals(fieldName)) {
return field;
}
}
// 否则,查看父类中是否存在此字段,如果有则返回
Class<?> superClazz = clazz.getSuperclass();
if (superClazz != null && superClazz != Object.class) {
//递归
return getFieldByName(fieldName, superClazz);
}
// 如果本类和父类都没有,则返回空
return null;
}
/**
* 根据字段名获取字段值
*
* @param fieldName 字段名
* @param o 对象
* @return 字段值
* @throws Exception 异常
*
*/
public static Object getFieldValueByName(String fieldName, Object o)
throws Exception {
Object value = null;
if("class java.util.HashMap".equals(o.getClass().toString())){
return ((Map)o).get(fieldName);
}
//根据字段名得到字段对象
Field field = getFieldByName(fieldName, o.getClass());
//如果该字段存在,则取出该字段的值
if (field != null) {
field.setAccessible(true);//类中的成员变量为private,在类外边使用属性值,故必须进行此操作
value = field.get(o);//获取当前对象中当前Field的value
} else {
throw new Exception(o.getClass().getSimpleName() + "类不存在字段名 "
+ fieldName);
}
return value;
}
/**
* 根据带路径或不带路径的属性名获取属性值,即接受简单属性名,
* 如userName等,又接受带路径的属性名,如Baby.department.name等
*
* @param fieldNameSequence 带路径的属性名或简单属性名
* @param o 对象
* @return 属性值
* @throws Exception 异常
*
*/
public static Object getFieldValueByNameSequence(String fieldNameSequence,
Object o) throws Exception {
Object value = null;
// 将fieldNameSequence进行拆分
String[] attributes = fieldNameSequence.split("\\.");
if (attributes.length == 1) {
value = getFieldValueByName(fieldNameSequence, o);
} else {
// 根据数组中第一个连接属性名获取连接属性对象,如Baby.department.name
Object fieldObj = getFieldValueByName(attributes[0], o);
//截取除第一个属性名之后的路径
String subFieldNameSequence = fieldNameSequence
.substring(fieldNameSequence.indexOf(".") + 1);
//递归得到最终的属性对象的值
value = getFieldValueByNameSequence(subFieldNameSequence, fieldObj);
}
return value;
}
/**
* 向工作表中填充数据
*
* @param sheet
* excel的工作表名称
* @param list
* 数据源
* @param fieldMap
* 中英文字段对应关系的Map
* @param style
* 表格中的格式
* @throws Exception
* 异常
*
*/
public static <T> void fillSheet(HSSFSheet sheet, List<T> list,
LinkedHashMap<String, String> fieldMap,HSSFCellStyle style,boolean merge) throws Exception {
// 定义存放英文字段名和中文字段名的数组
String[] enFields = new String[fieldMap.size()];
String[] cnFields = new String[fieldMap.size()];
// 填充数组
int count = 0;
for (Entry<String, String> entry : fieldMap.entrySet()) {
enFields[count] = entry.getKey();
cnFields[count] = entry.getValue();
count++;
}
//在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制short
HSSFRow row=sheet.createRow((int)0);
// 填充表头
for (int i = 0; i < cnFields.length; i++) {
HSSFCell cell=row.createCell(i);
cell.setCellValue(cnFields[i]);
cell.setCellStyle(style);
sheet.setColumnWidth(i, 6000);
//sheet.autoSizeColumn(i);
}
//生成对比数组
String[][] compares = new String[list.size()][enFields.length];
// 填充内容
for (int index = 0; index < list.size(); index++) {
row = sheet.createRow(index + 1);
// 获取单个对象
T item = list.get(index);
for (int i = 0; i < enFields.length; i++) {
Object objValue = getFieldValueByNameSequence(enFields[i], item);
String fieldValue = objValue == null ? "" : objValue.toString();
HSSFCell cell=row.createCell(i);
cell.setCellValue(fieldValue);
cell.setCellStyle(style);
//写入对比数组
for(int j=i;j<enFields.length;j++){
if(compares[index][j]==null){
compares[index][j] = fieldValue;
}else{
compares[index][j] += ";"+fieldValue;
}
}
}
}
//检查合并
if(merge){
for (int j = 0; j < enFields.length; j++){
int from = 0,to=0;
for(int i=0;i<list.size(); i++) {
if(compares[i][j].equals(compares[from][j])){
to = i;
}else{
if(from!=to){
CellRangeAddress cellRangeAddress = new CellRangeAddress(from+1, to+1, j, j);
sheet.addMergedRegion(cellRangeAddress);
}
from=to=i;
}
if(i+1==list.size() && from!=to){
CellRangeAddress cellRangeAddress = new CellRangeAddress(from+1, to+1, j, j);
sheet.addMergedRegion(cellRangeAddress);
}
}
}
}
}
public static String ShiftNull(Object obj){
if(obj==null)
return "";
return obj.toString();
}
}