随时随地阅读更多技术实战干货,获取项目源码、学习资料,请关注源代码社区公众号(ydmsq666)、博主微信(guyun297890152)、QQ技术交流群(183198395)。
在之前的一篇文章java操作Excel实战干货中展示了使用poi库读取excel表格的的用法,今天演示另一个常用功能,将数据导出到excel中,按照实战演练方式,后台提供接口,数据来源于数据库,然后导出excel输出到前端,详细如下:
接口代码:
@RequestMapping(value = "/test/excel/export", method = { RequestMethod.POST })
public ModelAndView exportExcel(@RequestBody RequestBean req) {
// 查询数据
List<Student> list = testService.getStudent(req);
try {
Map<String, Object> model = new HashMap<String, Object>();
// 表格名
model.put("sheetName", "学生表");
// 文件名
model.put("filename", "学生统计导出-" + new SimpleDateFormat("yyyyMMddhhmmss").format(new Date()) + ".xls");
// 设置需要导出的数据
model.put("items", list);
// 设置需要显示的表头
model.put("showName", new String[] { "学号", "姓名", "性别" });
// 设置表头对应的属性
model.put("fieldName", new String[] { "number", "name", "sex" });
return new ModelAndView(new PoiExcelView(), model);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("导出excel出错,请检查;异常为:" + e.getMessage());
}
}
上面有详细注释,主要设置表格的一些属性,以及查询需要导出的实际内容,service简单介绍:
public List<Student> getStudent(RequestBean req) {
// 为了方便测试直接制造数据,实际中应该从数据库中读取
List<Student> list = new ArrayList<Student>();
list.add(new Student("001", "张三", "男"));
list.add(new Student("002", "李四", "男"));
list.add(new Student("003", "小娟", "女"));
return list;
}
这里为了方便测试,直接写死的数据。
测试用的实体对象Student就三个属性,这里就列代码了。
excel核心控件:PoiExcelView:
package com.home.javaee.view;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
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.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.web.servlet.view.document.AbstractXlsView;
import com.home.base.data.CollectionUtil;
import com.home.base.data.StringUtil;
import com.home.base.reflect.ReflectUtil;
/**
* Excel控件
*
* @author fengjian
*
*/
public class PoiExcelView extends AbstractXlsView {
private static Logger logger = Logger.getLogger(PoiExcelView.class);
@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook arg1, HttpServletRequest request,
HttpServletResponse response) throws Exception {
OutputStream os = null;
String sheetName = "";
try {
// 获取表格名
sheetName = (String) model.get("sheetName");
// 获取文件名
String filename = (String) model.get("filename");
// 获取显示表头
String[] showName = (String[]) model.get("showName");
// 获取表头对应属性
String[] fieldName = (String[]) model.get("fieldName");
// 获取列表数据
List<?> list = (List<?>) model.get("items");
response.setContentType("APPLICATION/OCTET-STREAM");
String fileNameURL = URLEncoder.encode(filename, "UTF-8");
// 设置响应header
response.setHeader("Content-Disposition",
"attachment; filename=" + fileNameURL + ";filename*=utf-8''" + fileNameURL);
// 获取输入流
os = response.getOutputStream();
HSSFWorkbook workbook = (HSSFWorkbook) arg1;
// 创建表格
HSSFSheet sheet = workbook.createSheet(sheetName);
// 设置表格总标题和每列的表头以及样式
addTitle(sheet, showName, getHeaderStyle(workbook), getContentStyle(workbook));
// 设置表格内容以及样式
addContent(sheet, list, fieldName, getContentStyle(workbook), false, null);
// 输出表格
workbook.write(os);
} catch (Throwable e) {
logger.error(sheetName + "导出出错:" + e.getMessage(), e);
} finally {
try {
os.flush();
os.close();
} catch (Throwable e) {
logger.error(sheetName + "导出Excel出错:" + e.getMessage(), e);
}
}
}
/**
* 获取标题栏样式配置
*
* @param workbook
* @return
*/
public static HSSFCellStyle getHeaderStyle(HSSFWorkbook workbook) {
HSSFCellStyle format = workbook.createCellStyle();
// 创建字体
HSSFFont font = workbook.createFont();
// 加粗
font.setBold(true);
// 黑体
font.setFontName("黑体");
// 字体大小
font.setFontHeightInPoints((short) 16);
// 垂直居中
format.setVerticalAlignment(VerticalAlignment.CENTER);
// 水平居中
format.setAlignment(HorizontalAlignment.CENTER);
// 设置字体
format.setFont(font);
return format;
}
/**
* 获取内容样式配置
*
* @param workbook
* @return
*/
public static HSSFCellStyle getContentStyle(HSSFWorkbook workbook) {
HSSFCellStyle format = workbook.createCellStyle();
// 创建字体
HSSFFont font = workbook.createFont();
// 宋体
font.setFontName("宋体");
// 垂直居中
format.setVerticalAlignment(VerticalAlignment.CENTER);
// 水平居中
format.setAlignment(HorizontalAlignment.CENTER);
// 设置字体
format.setFont(font);
// 下边框样式
format.setBorderBottom(BorderStyle.THIN);
// 左边框样式
format.setBorderLeft(BorderStyle.THIN);
// 上边框样式
format.setBorderTop(BorderStyle.THIN);
// 右边框样式
format.setBorderRight(BorderStyle.THIN);
return format;
}
/**
* 给表格设置每列的标题,格式:表格第一行合并居中用表格名称作为整个表格的标题,第二行才作为列标题,第三行开始为实际内容
*
* @param sheet
* @param titleNames
* 列标题数组
* @param headerStyle
* 表格标题样式
* @param contentStyle
* 内容样式
*/
public static void addTitle(HSSFSheet sheet, String[] titleNames, HSSFCellStyle headerStyle,
HSSFCellStyle contentStyle) {
// 合并单元格,把第一行按照数据的列宽进行合并,然后用表格名称作为标题,比如'员工工资表'
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, titleNames.length - 1));
// 取出第一行
HSSFRow row = sheet.createRow(0);
// 第一格也就是合并后的整个单元格
HSSFCell cell = row.createCell(0);
// 用表格名作为主标题
cell.setCellValue(sheet.getSheetName());
// 设置标题样式
cell.setCellStyle(headerStyle);
// 用第二行作为每列数据的列标题
row = sheet.createRow(1);
// 循环填充列标题,并设置样式
for (int i = 0; i < titleNames.length; i++) {
cell = row.createCell(i);
cell.setCellValue(titleNames[i]);
cell.setCellStyle(contentStyle);
}
}
/**
* 添加表格内容,从指定行开始
*
* @param sheet
* @param list
* 列表数据
* @param fieldName
* 属性名数组
* @param contentStyle
* 内容样式
* @param isHaveSerial
* 是否添加序号
* @param startRow
* 起始行
*/
public static <T> void addContent(HSSFSheet sheet, List<T> list, String[] fieldName, HSSFCellStyle contentStyle,
boolean isHaveSerial, Integer startRow) {
try {
if (CollectionUtil.isEmpty(list)) {
return;
}
if (startRow == null) {
startRow = 2;
}
HSSFRow row = null;
HSSFCell cell = null;
List<T> tList = (List<T>) list;
T t = null;
String value = "";
// 填充列表内容
for (int i = 0; i < list.size(); i++) {
row = sheet.createRow(i + startRow);
t = tList.get(i);
for (int j = 0; j < fieldName.length; j++) {
value = StringUtil.objectToString(getFieldValueByName(t, fieldName[j]));
if (isHaveSerial) {
// 首列加序号
if (row.getCell(0) != null && row.getCell(0).getStringCellValue() != null) {
cell = row.createCell(0);
cell.setCellValue("" + i);
}
cell = row.createCell(j + 1);
} else {
cell = row.createCell(j);
}
cell.setCellValue(value);
cell.setCellStyle(contentStyle);
}
}
for (int j = 1; j < fieldName.length; j++) {
sheet.autoSizeColumn(j); // 单元格宽度 以最大的为准
}
} catch (Throwable e) {
logger.error("填充内容出现错误:" + e.getMessage(), e);
}
}
/**
* 获取某个对象指定属性的值
*
* @param obj
* @param fieldName
* @return
*/
@SuppressWarnings("rawtypes")
public static Object getFieldValueByName(Object obj, String fieldName) {
try {
if (StringUtil.isEmpty(fieldName) || obj == null) {
return null;
}
Object fieldValue = null;
// 如果是map直接根据key取值
if (obj instanceof Map) {
Map map = (Map) obj;
fieldValue = map.get(fieldName);
} else {
// 如果是javabean对象,直接反射调用getter方法
fieldValue = ReflectUtil.invokeGetterMethod(obj, fieldName);
}
return fieldValue;
} catch (Throwable e) {
logger.error("获取属性值出现异常:" + e.getMessage(), e);
return null;
}
}
}
下面进行简要解析
buildExcelDocument是构造表格的核心回调方法,首先获取外部传过来的一些数据和属性,然后在用这些数据设置表头和列表内容,然后进行输出
getHeaderStyle是自定义的表格第一行名称的样式
getContentStyle是自定义表格内容列表包括表头的样式
addTitle是添加标题和表头,作为表格的第一第二行
addContent是添加表格的实际列表内容
上面是涉及到表格导出的一些核心方法,另外还涉及到一些辅助方法,简单介绍下:
getFieldValueByName作用是对数据库查出来的数据对象,根据其属性获取对应的值,比如根据name属性获取值'张三'
ReflectUtil的invokeGetterMethod是一个工具方法,反射调用某个属性的getter方法,相关方法如下:
/**
* 反射执行某个属性的getter方法
*
* @param obj
* @param fieldName
* @return
*/
public static Object invokeGetterMethod(Object obj, String fieldName) {
return invokeMethod(obj, getGetterName(fieldName), null);
}
/**
* 根据getter方法的规则,构造某个属性的get方法名
*
* @param fieldName
* 属性名
* @return
*/
public static String getGetterName(String fieldName) {
// 属性的第一个字母转换为大写
return "get" + fieldName.substring(0, 1) + fieldName.substring(1);
}
/**
* 通过类对象,运行指定方法
*
* @param obj
* 类对象
* @param methodName
* 方法名
* @param params
* 参数值
* @return 失败返回null
*/
public static Object invokeMethod(Object obj, String methodName, Object[] params) {
if (obj == null || StringUtil.isEmpty(methodName)) {
logger.error("参数异常,obj=" + obj + ",methodName=" + methodName);
return null;
}
Class<?> clazz = obj.getClass();
try {
Class<?>[] paramTypes = null;
if (params != null) {
paramTypes = new Class[params.length];
for (int i = 0; i < params.length; ++i) {
paramTypes[i] = params[i].getClass();
}
}
Method method = clazz.getMethod(methodName, paramTypes);
method.setAccessible(true);
return method.invoke(obj, params);
} catch (NoSuchMethodException e) {
logger.error("method " + methodName + " not found in " + obj.getClass().getName());
} catch (Exception e) {
logger.error("Exception:" + e);
}
return null;
}
StringUtil的objectToString用来将其他数据类型统一成字符串,便于显示:
/**
* 将对象转换为字符串
*
* @param object
* @return
*/
public static String objectToString(Object object) {
if (object == null) {
return "";
}
if (object instanceof Date) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return df.format((Date) object);
}
if (object instanceof String) {
return (String) object;
}
if (object instanceof Integer) {
return ((Integer) object).intValue() + "";
}
if (object instanceof Double) {
return ((Double) object).doubleValue() + "";
}
if (object instanceof Long) {
return Long.toString(((Long) object).longValue());
}
if (object instanceof Float) {
return Float.toHexString(((Float) object).floatValue());
}
if (object instanceof Boolean) {
return Boolean.toString((Boolean) object);
}
if (object instanceof Short) {
return Short.toString((Short) object);
}
return object.toString();
}
到此为止,介绍完毕,这些方法都是非常实用的干货,每个方法都有详细的注释,一目了然。