在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,待以后有时间,再来写博。

  无他,唯手熟尔~

使用Apache poi来编写导出excel的工具类的更多相关文章

  1. apache poi根据模板导出excel

    需要预先新建编辑好一个excel文件,设置好样式. 编辑好输出的数据,根据excel坐标一一对应. 支持列表数据输出,列表中列合并. 代码如下: package com.icourt.util; im ...

  2. 使用POI插件,提取导出excel的工具类

    在网站的不同的模块都需要使用到导入导出excel的功能,我们就需要写一个通用的工具类ExcelUtil. 我的思路:首先,导入和导出的Excel的文件格式固定:主标题,二级标题,数据行(姑且就这么叫) ...

  3. POI导入导出excel(附工具类)

    关于POI导出excel的功能我在前面的文章已经写过了,POI导出excel的三种方式 , 导出表格数据到excel并下载(HSSFWorkbook版) ,本篇文章主要是将导入导出功能进一步地封装,在 ...

  4. 导出excel文件工具类

    package com.rrz.common.utils.excel; import java.io.IOException;import java.io.OutputStream;import ja ...

  5. java web 导出Excel 的工具类公用实现

    平时我们在开发后端管理的系统时,经常会出现导出Excel的功能.这个功能很简单,但是呢,我一般就会使用公用的工具类,在一个Util中,暴露公用导出的接口,只需要传入需要导出的数据(一般使用list集合 ...

  6. 导出excel(利用工具类导出excel)

    /** * 添加导出功能 * @param creditPageResult * @param request * @param response */ @RequestMapping(value = ...

  7. Java使用POI实现数据导出excel报表

    Java使用POI实现数据导出excel报表 在上篇文章中,我们简单介绍了java读取word,excel和pdf文档内容 ,但在实际开发中,我们用到最多的是把数据库中数据导出excel报表形式.不仅 ...

  8. excel读取 工具类

    package cn.yongche.utils; import java.io.File; import java.io.FileInputStream; import java.io.IOExce ...

  9. Java 使用Apache POI读取和写入Excel表格

    1,引入所用的包 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxm ...

随机推荐

  1. Android面试6家一线大厂,这个问题是必问!

    年后面了六家大厂,每家都会问的一个问题就是Android的消息机制!可见Android的消息机制是多么重要! 消息机制之所以这么重要是因为Android应用程序是通过消息来驱动的,Android某种意 ...

  2. 解决ftp登录问题:500 OOPS: cannot change directory:/home/xxx 500 OOPS: child died

    .personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...

  3. git submodule 操作

    git submodule foreach git status 举一反三,对所有子库的操作,都可以使用 git submodule foreach 做前缀 foreach,可以记忆为for each ...

  4. 使用账号密码来操作github? NO!

    目录 简介 背景介绍 创建令牌 使用令牌 缓存令牌 使用GCM 总结 简介 最近在更新github文件的时候,突然说不让更新了,让我很是困惑,原因是在2021年8月13号之后,github已经不让直接 ...

  5. PLSQL编程及存储过程的创建

    一.PLSQL的初步介绍 PLSQL是使sql具有处理过程的能力,可以分为三个部分:声明部分.可执行部分.异常处理部分 1.如何使用PLSQL打印Hello World! 在sqlplus里中打印   ...

  6. SpringBoot开发九-生成验证码

    需求介绍-生成验证码 先生成随机字符串然后利用Kaptcha API生成验证图片 代码实现 先在pom.xml引入 <dependency> <groupId>com.gith ...

  7. Azure Bicep 开发利器

    Bicep 是一种用于声明式部署Azure资源的领域特定语言.它的目标是通过更清晰的语法.改进的类型安全性.以及对模块化和代码重用的更好支持,彻底简化编写体验. Bicep 其实是对 ARM 模板的透 ...

  8. BaiduSpider:爬取百度的利器

    视频链接:https://www.zhihu.com/zvideo/1272864710321516544 BaiduSpider是一个能够爬取百度搜索结果的Python爬虫,轻量但强大.目前支持百度 ...

  9. S3C2440—6.串口的printf实现

    文章目录 一.框架 二.printf函数原理 2.1 printf的声明 2.2 参数解读 2.3 如何得到可变参数的值 2.4 解决变参的宏定义 2.5 完成printf函数的封装 三.结合UART ...

  10. 题解 big

    传送门 题面里那个式子 考场上我推了半天那个式子代表什么意思,但就是没想到位运算 \(\lfloor \frac{2x}{2x^n} \rfloor \iff x\gg(n-1)\), 即将x的第n位 ...