随时随地技术实战干货,获取项目源码、学习资料,请关注源代码社区公众号(ydmsq666)
在之前的一篇文章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);@Overrideprotected 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");// 设置响应headerresponse.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();}
到此为止,介绍完毕,这些方法都是非常实用的干货,每个方法都有详细的注释,一目了然。