使用Apache poi来编写导出excel的工具类

时间:2023-03-09 01:45:34
使用Apache poi来编写导出excel的工具类

  在JavaWeb开发的需求中,我们会经常看到导出excel的功能需求,然后java并没有提供操作office文档的功能,这个时候我们就需要使用额外的组件来帮助我们完成这项功能了。

  很高兴Apache基金会为我们提供了poi组件,让我们能够在它的帮助下操作office文档,下面就让我们来使用它来制作导出excel的工具类。

一.前期说明

  1.我们要理解一个excel文件的组成。首先要有一个workbook(工作簿,即excel文件本身),然后workbook中有几个sheet(工作表),sheet中是由row(行)和column(列)构成的,行和列确定一个唯一的cell(单元格)。理清了这几个组成部分,我们再来理解工具类的就比较容易了。

  2.office文件分为97-2003版本和2007版本,这两个是截然不同的文件格式,97-2003的excel的文件后缀为.xsl,而2007的excel的文件后缀为.xslx。我们在这里暂时只讨论97-2003版本的操作,至于2007版本的操作原理相同,只是所使用的类不同。

  3.操作excel 97-2003的文件,只需要引入poi-version-yyyymmdd.jar(例如poi-3.13-20150929.jar)即可,其余jar包的用途,详情请百度“poi各jar包的作用”。

二.代码分析

工具类代码

package com.billionsfinance.bqap.util;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import com.billionsfinance.common.Exception.BizException; public class ExportExcel<T> {
/**
* 利用了JAVA的反射,将javabean的集合以一定的格式输出到指定IO设备上
* @param sheetName
* 工作表标题
* @param headerMap
* 表格列属性及属性名
* @param dataset
* 需要显示的数据集合
* @param out
* 与输出设备相关的流对象,可以将Excel文档导出到本地或者网络中
* @throws IOException
*/
public void exportExcel(String sheetName, Map<String, String> headerMap, Collection<T> dataset, OutputStream out) throws IOException{
//声明一个工作簿
HSSFWorkbook workbook = new HSSFWorkbook();
//生成一个工作表
HSSFSheet sheet = workbook.createSheet(sheetName);
//设置表格默认列宽
     //有些资料在设置宽度的时候用的是short(20),但是在实际使用的过程中,short(20)已不被推荐使用,所以直接使用int(20)
sheet.setDefaultColumnWidth(20);
//获取属性Map的keySet
Set<String> keySet = headerMap.keySet();
//获取属性的数组
String[] fields = keySet.toArray(new String[0]);
//对keySet进行遍历,获得属性对应的属性名称来构成标题行
Iterator<String> key = keySet.iterator();
//第一行
HSSFRow row = sheet.createRow(0);
for(int i = 0; key.hasNext(); i++){
//产生单元格
HSSFCell cell = row.createCell(i);
//产生标题行
HSSFRichTextString title = new HSSFRichTextString(headerMap.get(key.next()));
       //为单元格赋值
cell.setCellValue(title);
}
/*
* 遍历数据集合,产生数据行
* 利用反射,通过属性数组来拼接getXXX的方法去获得属性的值
*/
//属性数组的长度
int size = fields.length;
//属性的get方法的数组
String[] methodNames = new String[size];
for(int i = 0; i < size; i++){
       //拼凑属性的getter方法
methodNames[i] = "get"
+ fields[i].substring(0, 1).toUpperCase()
+ fields[i].substring(1);
}
Iterator<T> ite = dataset.iterator();
for(int index = 1; ite.hasNext(); index++){
row = sheet.createRow(index);
T t = (T)ite.next();
for(int i = 0; i < size; i++){
HSSFCell cell = row.createCell(i);
try{
Class<?> cla = t.getClass();
Method getMethod = cla.getMethod(methodNames[i], new Class[]{});
Object value = getMethod.invoke(t, new Object[]{});
String textValue = null;
//如果属性的类型是String,则直接输出字符串
if(value instanceof String){
textValue = value.toString();
}
//如果属性的类型是Date,则需要格式转换
if(value instanceof Date){
Date date = (Date)value;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
textValue = sdf.format(date);
}
//如果是字符串,则利用正则表达式判断textValue是否全部由数字组成
if(textValue != null){
Pattern p = Pattern.compile("^//d+(//.//d+)?$");
Matcher matcher = p.matcher(textValue);
if(matcher.matches()){
//是数字则当做double处理
cell.setCellValue(Double.parseDouble(textValue));
}else{
HSSFRichTextString richString = new HSSFRichTextString(textValue);
cell.setCellValue(richString);
}
}
}catch(BizException ex){
LogUtil.ERROR.error(ex, ex);
}catch(Exception e){
e.printStackTrace();
}
}
}
     //将workbook输出到输出设备中
workbook.write(out);
     //关闭资源
out.flush();
workbook.close();
out.close();
}
}

代码说明:

  1.sheetName,即工作表的名称,这里只有一张工作表,所以一次导出的数据不能多于6万条。注意,一次导出太多的数据会消耗服务器内存,有可能导致服务器宕机,所以导出的时候最好对一次性导出数据量做限定。

  2.headerMap,此处应传入一个linkedHashMap<String,String>对象。linkedHashMap既指定了属性和属性名称(name:名称)的对应关系,又保证了属性与属性之间的顺序,这样我们就能很好的控制在excel表格中属性的顺序。

  3.dataset,这里使用的泛型,不过我们在实际开发中一般都是使用List集合来传递数据。

  4.out,输出设备,这里既可以是屏幕设备,也可以是文件系统,当然也可以是response响应了。

代码分析:

  1.在代码中,我尽量避免使用java的反射,因为反射是比较消耗性能的操作,只在获得getter方法的时候使用了反射。属性的获取是通过headerMap遍历,这么做一是节省了性能的消耗,二是可以指定需要导出的属性。例如我们在使用实体类的时候经常需要序列化,那么实体类中肯定有serialVersionUID属性,然而这个属性我们是不需要导出的。我们只需要在headerMap中添加需要导出的属性即可做到剔除不需要的属性。

三.使用

1.bean

package com.billionsfinance.bqap.bean;

public class SmsFailHistoryBean implements java.io.Serializable{
private static final long serialVersionUID = 5449014577923870013L;
private String id; //流水号
private String sender; //发送人
private String appTypeName; //发送类型名称
private String phone; //手机号码
private String message; //短信内容
private String supplier; //供应商
private String status; //状态
private String submitTime; //提交时间
private String createTime; //创建时间
private String appType; //发送类型

/* getter and setter */
}

  这个bean就是我们在项目开发中经常使用的实体类,看,其中就有serialVersionUID属性,可是我们并不需要导出它。

2.controller

package com.billionsfinance.bqap.controller;

import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import com.billionsfinance.bqap.bean.SmsFailHistoryBean;
import com.billionsfinance.bqap.bean.SmsSendHistoryBean;
import com.billionsfinance.bqap.entity.Config;
import com.billionsfinance.bqap.server.IConfigServer;
import com.billionsfinance.bqap.server.ISmsSendHistoryServer;
import com.billionsfinance.bqap.util.Constant;
import com.billionsfinance.bqap.util.ExportExcel;
import com.billionsfinance.bqap.util.GroupNoManager;
import com.billionsfinance.bqap.util.StringUtils;
import com.billionsfinance.common.framework.RemoteCall; /**
* 导出excel表格,暂时只能用来导出发送历史和失败历史
* @author XIAYU
*
*/
@Controller
public class ExportExcelController {
/*
* 导出发送历史或发送失败历史
*/
@RequestMapping("/exportExcel")
public void exportExcel(HttpServletRequest request, HttpServletResponse response) throws Exception{
ISmsSendHistoryServer smsSendHistoryServer = RemoteCall.getRemoteObject(ISmsSendHistoryServer.class, GroupNoManager.getGroupNo(request));
//获取查询条件
Map<String, String> params = this.setParmas(request);
//文件名称
String filename = null;
response.reset();
     //设置响应头
response.setContentType("application/msexcel");
OutputStream out = response.getOutputStream();
Map<String, String> headerMap = new LinkedHashMap<String, String>();
String page = request.getParameter("page");
//如果是smsSendHistoryList.jsp页面发过来的则导出发送历史
if("smsSendHistory".equals(page)){
filename = "发送历史总记录.xls";
response.setHeader("Content-disposition", "attachment;filename=" + encodeChineseDownloadFileName(request, filename));
ExportExcel<SmsSendHistoryBean> ex = new ExportExcel<SmsSendHistoryBean>();
headerMap.clear();
headerMap.put("id", "流水号");
headerMap.put("sender", "发送人");
headerMap.put("appTypeName", "发送类型");
headerMap.put("message", "短信内容");
headerMap.put("supplier", "供应商");
headerMap.put("success", "成功条数");
headerMap.put("successPhone", "成功手机号码");
headerMap.put("failure", "失败条数");
headerMap.put("failurePhone", "失败手机号码");
headerMap.put("count", "总条数");
headerMap.put("sendTime", "定时发送时间");
headerMap.put("createTime", "创建时间");
       //获取需要导出的数据集合
List<SmsSendHistoryBean> dataset = smsSendHistoryServer.listSmsSendHistory(params);
ex.exportExcel("发送历史", headerMap, dataset, out);
}
//如果是smsFailHistoryList.jsp页面发过来的则导出失败历史
if("smsFailHistory".equals(page)){
filename = "短信发送失败记录.xls";
response.setHeader("Content-disposition", "attachment;filename=" + encodeChineseDownloadFileName(request, filename));
ExportExcel<SmsFailHistoryBean> ex = new ExportExcel<SmsFailHistoryBean>();
headerMap.clear();
headerMap.put("id", "流水号");
headerMap.put("sender", "发送人");
headerMap.put("appTypeName", "发送类型");
headerMap.put("phone", "手机号码");
headerMap.put("message", "短信内容");
headerMap.put("supplier", "供应商");
headerMap.put("status", "状态");
headerMap.put("submitTime", "提交时间");
headerMap.put("createTime", "创建时间");
       //获取需要导出的数据集合
List<SmsFailHistoryBean> dataset = smsSendHistoryServer.listSmsFailHistory(params);
ex.exportExcel("失败历史", headerMap, dataset, out);
}
if(out != null){
out.flush();
out.close();
}
} /**
* 根据配置代码获取获取枚举参数信息
* @param request
* @param configCode
* @return
*/
private List<Config> getConfigList(HttpServletRequest request,String configCode){
IConfigServer configserver =RemoteCall.getRemoteObject(IConfigServer.class, GroupNoManager.getGroupNo(request));
Config config = new Config();
config.setConfigCode(configCode);
List<Config> list = configserver.listConfig(config);
return list;
} /**
* 存储参数,保证页面刷新后参数还在查询栏中
* 返回参数,用于查询条件
* @param request
* @return
*/
private Map<String,String> setParmas(HttpServletRequest request){
List<Config> appList = this.getConfigList(request, Constant.APP_CODE);
request.setAttribute("appList", appList);
List<Config> supplierList = this.getConfigList(request, Constant.SUPPLIER_CODE);
request.setAttribute("supplierList", supplierList);
Map<String,String> params = new HashMap<String,String>();
String appCode = request.getParameter("appCode");
String sendType = request.getParameter("sendType");
String supplier = request.getParameter("supplier");
String startTime = request.getParameter("startTime");
String endTime = request.getParameter("endTime");
params.put("appCode", appCode);
params.put("sendType", sendType);
params.put("supplier", supplier);
params.put("startTime", startTime);
params.put("endTime", endTime);
request.setAttribute("appCode", appCode);
request.setAttribute("sendType", sendType);
request.setAttribute("supplier", supplier);
request.setAttribute("startTime", startTime);
request.setAttribute("endTime", endTime);
return params;
}

  /**
  * 解决浏览器对文件中文名的编码问题
   * 不同的浏览器对文件中文名的处理方法不同
   * @param request
   * @param pFileName
   * @return
   */
private String encodeChineseDownloadFileName(HttpServletRequest request, String pFileName) throws UnsupportedEncodingException{
String filename = null;
//获取请求头中的浏览器标识
String agent = request.getHeader("USER-AGENT");
if(agent != null){
if(agent.indexOf("Firefox") != -1){
//Firefox
filename = "=?UTF-8?B?" +
(new String(Base64.encodeBase64(pFileName.getBytes("UTF-8")))) + "?=";
}else if(agent.indexOf("Chrome") != -1){
//Chrome
filename = new String(pFileName.getBytes(), "ISO8859-1");
}else{
//IE7+
filename = URLEncoder.encode(pFileName, "UTF-8");
//替换空格
filename = StringUtils.replace(filename, "+", "%20");
}
}else{
filename = pFileName;
}
return filename;
}
}

  这个Controller是springMVC下的Controller,使用注解。hessian调用不必太在意,主要是注意调用工具类时应传入的参数。

3.html

<a href="${ctx}/exportExcel.action?page=smsFailHistory&params=...">导出</a>

  只需要一个a标签或者一个button就能触发导出事件呢,这里要注意一个问题,在导出操作的时候不要使用ajax方式,否则返回的文件数据会直接打印到浏览器的后台中,而不是输出到文件系统。所以就按照平常的方式去提交请求就好。

四.总结

  这只是poi操作excel文件的一个简单示例,它还有其他很多的功能,比如改变excel数据的样式,编写excel公式等等,,,这些功能需要熟读api,待以后有时间,再来写博。

  无他,唯手熟尔~