我做过的一个项目,需要实现在线实时生成 Excel文件供客户端下载的需求,最初考虑的是先在服务器端生成真实的文件,然后在客户端下载该文件。后来发现这样做不但性能不够好、速度较慢,而且还要占用服务器空间。所以采取了在服务器端生成文件输出流(ServletOutputStream),通过HttpServletResponse对象设置相应的响应头,然后将此输出流传往客户端的方法实现。在实现过程中,用到了Apache组织的Jakarta开源组件POI,读者朋友可到其官方网站查阅相关资料及下载。现将整个实现过程介绍如下:
一、首先,根据Excel表的特点,我编写了一个Excel表模型类ExcelModel,代码如下:
package
com.qnjian.myprj.excel;
import
java.util.ArrayList;
/** */
/**
*
* <p>Title: ExcelModel</p>
*
* <p>Description: Excel表的操作模型</p>
*
* <p>Copyright: Copyright (c) 2005-10-20</p>
*
* <p>Company: *** </p>
*
* @author zzj
* @version 1.0
*/
public
class
ExcelModel
{
/** */ /**
* 文件路径,这里是包含文件名的路径
*/
protected String path;
/** */ /**
* 工作表名
*/
protected String sheetName;
/** */ /**
* 表内数据,保存在二维的ArrayList对象中
*/
protected ArrayList data;
/** */ /**
* 数据表的标题内容
*/
protected ArrayList header;
/** */ /**
* 用于设置列宽的整型数组
* 这个方法在程序中暂未用到
* 适用于固定列数的表格
*/
protected int [] width;
public ExcelModel()
{
path = " report.xls " ;
}
public ExcelModel(String path)
{
this .path = path;
}
public void setPath(String path)
{
this .path = path;
}
public String getPath()
{
return this .path;
}
public void setSheetName(String sheetName)
{
this .sheetName = sheetName;
}
public String getSheetName()
{
return this .sheetName;
}
public void setData(ArrayList data)
{
this .data = data;
}
public ArrayList getData()
{
return this .data;
}
public void setHeader(ArrayList header)
{
this .header = header;
}
public ArrayList getHeader()
{
return this .header;
}
public void setWidth( int [] width)
{
this .width = width;
}
public int [] getWidth()
{
return this .width;
}
}
二、编写一个下载接口ExcelDownLoad,定义基本的方法:
package
com.qnjian.myprj.excel;
import
java.io.IOException;
import
java.util.List;
import
javax.servlet.http.HttpServletResponse;
import
org.apache.struts.action.ActionForm;
public
interface
ExcelDownLoad
{
/** */ /**
* 初始化要生成的Excel的表模型
* @param list List 填充了 Excel表格数据的集合
* @param form ActionForm及其子类
* @param excel ExcelModel Excel表的对象模型
* @see ExcelModel
* @throws Exception
*/
public ExcelModel createDownLoadExcel (List list, ActionForm form,
ExcelModel excel) throws Exception;
/** */ /**
* 在已文件已存在的情况下,采用读取文件流的方式实现左键点击下载功能,
* 本系统没有采取这个方法,而是直接将数据传往输出流,效率更高。
* @param inPutFileName 读出的文件名
* @param outPutFileName 保存的文件名
* @param HttpServletResponse
* @see HttpServletResponse
* @throws IOException
*/
public void downLoad(String inPutFileName, String outPutFileName,
HttpServletResponse response) throws IOException ;
/** */ /**
* 在已文件不存在的情况下,采用生成输出流的方式实现左键点击下载功能。
* @param outPutFileName 保存的文件名
* @param out ServletOutputStream对象
* @param downExcel 填充了数据的ExcelModel
* @param HttpServletResponse
* @see HttpServletResponse
* @throws Exception
*/
public void downLoad(String outPutFileName, ExcelModel downExcel,
HttpServletResponse response) throws Exception ;
}
三、编写一个实现了以上接口的公共基类BaseExcelDownLoad,并提供downLoad()方法的公共实现,代码如下:
/** */
/**
*
* <p>Title: BaseExcelDownLoad</p>
*
* <p>Description:Excel表下载操作基类,生成excel格式的文件流供下载 </p>
*
* <p>Copyright: Copyright (c) 2005-10-27</p>
*
* <p>Company: *** </p>
*
* @author zzj
* @version 1.0
*/
package
com.qnjian.myprj.excel;
import
java.io.FileInputStream;
import
java.io.IOException;
import
java.io.InputStream;
import
java.io.OutputStream;
import
java.util.List;
import
javax.servlet.http.HttpServletResponse;
import
org.apache.struts.action.ActionForm;
public
abstract
class
BaseExcelDownLoad
implements
ExcelDownLoad
{
/** */ /**
* 初始化要生成的Excel的表模型
* @param list List 填充了 Excel表格数据的集合
* @param form ActionForm及其子类
* @param excel ExcelModel Excel表的对象模型
* @see ExcelModel
* @throws Exception
*/
public abstract ExcelModel createDownLoadExcel (List list, ActionForm form,
ExcelModel excel) throws Exception;
/** */ /**
* 在已文件已存在的情况下,采用读取文件流的方式实现左键点击下载功能,
* 本系统没有采取这个方法,而是直接将数据传往输出流,效率更高。
* @param inPutFileName 读出的文件名
* @param outPutFileName 保存的文件名
* @param HttpServletResponse
* @see HttpServletResponse
* @throws IOException
*/
public void downLoad(String inPutFileName, String outPutFileName,
HttpServletResponse response) throws IOException
{
// 打开指定文件的流信息
InputStream is = new FileInputStream(inPutFileName);
// 写出流信息
int data = - 1 ;
OutputStream outputstream = response.getOutputStream();
// 清空输出流
response.reset();
// 设置响应头和下载保存的文件名
response.setHeader( " content-disposition " , " attachment;filename= " + outPutFileName);
// 定义输出类型
response.setContentType( " APPLICATION/msexcel " );
while ( (data = is.read()) != - 1 )outputstream.write(data);
is.close();
outputstream.close();
response.flushBuffer();
}
/** */ /**
* 在文件不存在的情况下,采用生成输出流的方式实现左键点击下载功能。
* @param outPutFileName 保存的文件名
* @param out ServletOutputStream对象
* @param downExcel 填充了数据的ExcelModel
* @param HttpServletResponse
* @see HttpServletResponse
* @throws Exception
*/
public void downLoad(String outPutFileName, ExcelModel downExcel,
HttpServletResponse response) throws Exception
{
// 取得输出流
OutputStream out = response.getOutputStream();
// 清空输出流
response.reset();
// 设置响应头和下载保存的文件名
response.setHeader( " content-disposition " , " attachment;filename= " + outPutFileName);
// 定义输出类型
response.setContentType( " APPLICATION/msexcel " );
ExcelOperator op = new ExcelOperator();
// out:传入的输出流
op.WriteExcel( downExcel,out);
out.close();
// 这一行非常关键,否则在实际中有可能出现莫名其妙的问题!!!
response.flushBuffer(); // 强行将响应缓存中的内容发送到目的地
}
}
请注意,BaseExcelDownLoad只是提供了一个生成下载文件流的公共方法,而Excel表格需要显示的数据内容则因具体业务与需求的不同而不同,因此,必须根据具体情况提供一个该类的继承类,实现createDownLoadExcel()方法,以生成所需要输出内容的Excel表格文件流。要说明的是,该方法的参数list ,实际上是一个ArrayList集合,我们将从数据库查询出来的记录集保存在其中,我想这是很容易做到的,实现的方式也可以各种各样。我项目中是通过Hibernate的Query对象取得查询结果集,它正好也是一个ArrayList。不同的客户,甚至不同功能模块内需要生成的Excel报表的内容都是不一样的。下面还是给出我的一个实现类作为例子吧。
四、实现按照具体的需求生成Excel表格文件流的类举例:继承自BaseExcelDownLoad类的AgentInfoExcelDownLoad:
package
com.qnjian.myprj.excel;
import
java.math.BigDecimal;
import
java.util.ArrayList;
import
java.util.Iterator;
import
java.util.List;
import
org.apache.struts.action.ActionForm;
import
com.netease.bid.cs.model.BidAgent;
import
com.netease.bid.cs.model.BidUser;
import
com.netease.bid.cs.util.DateUtil;
import
com.netease.bidding.cs.servlet.InitCsServlet;
import
com.netease.bidding.cs.vo.InstructUser;
public
class
AgentInfoExcelDownLoad
extends
BaseExcelDownLoad
{
public ExcelModel createDownLoadExcel(List list, ActionForm form,
ExcelModel excel) throws Exception
{
String titleStr = " 客户帐号;公司名称;所属地区;帐户余额;注册日期;联系方式;联系人; " ;
ArrayList data = new ArrayList();
Iterator ir = list.iterator();
while (ir.hasNext())
{
ArrayList rowData = new ArrayList();
BidAgent user = (BidAgent)ir.next();
rowData.add(user.getName());
rowData.add(user.getCorpName());
// 取得所在省名称
String provStr = (user.getProvince() == null ) ?
"" : InitCsServlet.getProvinceStr(
user.getProvince());
// 取得所在地区名称
String cityStr = (user.getCity() == null ) ?
"" : InitCsServlet.getCityStr(
user.getCity());
if (provStr.equals(cityStr))
{
cityStr = "" ;
}
rowData.add(provStr + " " + cityStr);
BigDecimal balance = user.getReturnBalance().add(user.getBalance());
rowData.add(balance);
String date = DateUtil.getFormatDate(user.getCreateTime(), " yyyy-MM-dd " );
rowData.add(date);
rowData.add(user.getPhone());
rowData.add(user.getLinkMan());
data.add(rowData);
}
String[] titles = titleStr.split( " ; " );
/**/ /* for(int i=0;i<titles.length;i++){
System.out.print(titles[i]+" ");
} */
ArrayList header = new ArrayList();
for ( int i = 0 ;i < titles.length;i ++ )
{
header.add(titles[i]);
}
// 设置报表标题
excel.setHeader(header);
// 设置报表内容
excel.setData(data);
return excel;
}
}
五、编写一个操作类,进行生成下载文件流的操作:
/** */
/**
*
* <p>Title: Excel表操作</p>
*
* <p>Description:用于生成Excel格式文件 </p>
*
* <p>Copyright: Copyright (c) 2005-10-20</p>
*
* <p>Company: *** </p>
*
* @author zzj
* @version 1.0
*/
package
com.qnjian.myprj.excel;
import
org.apache.poi.hssf.usermodel.
*
;
import
java.io.FileOutputStream;
import
java.io.BufferedOutputStream;
import
java.util.ArrayList;
import
java.math.BigDecimal;
import
java.io.OutputStream;
/** */
/**
*实现生成Excel文件的操作
*/
public
class
ExcelOperator
{
/** */ /**
* 将数据信息写入到Excel表文件,采取自建输出流的方式。
* @param excel ExcelModel Excel表的模型对象
* @throws Exception
*/
public void WriteExcel(ExcelModel excel) throws Exception
{
try
{
String file = excel.getPath();
// 新建一输出文件流
FileOutputStream fOut = new FileOutputStream(file);
BufferedOutputStream bf = new BufferedOutputStream(fOut);
HSSFWorkbook workbook = this .getInitWorkbook(excel);
// 把相应的Excel 工作簿存盘
workbook.write(fOut);
fOut.flush();
bf.flush();
// 操作结束,关闭文件
bf.close();
fOut.close();
// System.out.println("Done!");
} catch (Exception e)
{
// System.out.print("Failed!");
throw new Exception(e.getMessage());
}
}
/** */ /**
* 将数据信息写入到Excel表文件 ,采取传入输出流的方式。
* @param excel Excel表的模型对象
* @param out OutputStream 输出流
* @throws Exception
*/
public void WriteExcel(ExcelModel excel,OutputStream out) throws Exception
{
try
{
HSSFWorkbook workbook = this .getInitWorkbook(excel);
workbook.write(out);
out.close();
// System.out.println("Done!");
} catch (Exception e)
{
// System.out.println("Failed!");
throw new Exception(e.getMessage());
}
}
/** */ /**
* 取得填充了数据的工作簿
* @param excel ExcelModel Excel表的模型对象
* @return HSSFWorkbook 工作簿对象
*/
private HSSFWorkbook getInitWorkbook(ExcelModel excel)
{
// 创建新的Excel 工作簿
HSSFWorkbook workbook = new HSSFWorkbook();
// 在Excel工作簿中建一工作表
HSSFSheet sheet = null ;
String sheetName = excel.getSheetName();
if (sheetName != null )sheet = workbook.createSheet(sheetName);
else sheet = workbook.createSheet();
// 设置表头字体
HSSFFont font_h = workbook.createFont();
font_h.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
// 设置格式
HSSFCellStyle cellStyle = workbook.createCellStyle();
cellStyle.setFont(font_h);
// 在索引0的位置创建行(最顶端的行)
HSSFRow row = sheet.createRow(( short ) 0 );
ArrayList header = excel.getHeader();
if (header != null )
{
for ( int i = 0 ;i < header.size();i ++ )
{
// 在索引0的位置创建单元格(左上端)
HSSFCell cell = row.createCell(( short )i);
// 定义单元格为字符串类型
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
// 设置解码方式
cell.setEncoding(( short ) 1 );
// 设置单元格的格式
cell.setCellStyle(cellStyle);
// 在单元格中写入表头信息
cell.setCellValue((String)header.get(i));
}
}
ArrayList cdata = excel.getData();
for ( int i = 0 ;i < cdata.size();i ++ )
{
// 从第二行开始
HSSFRow row1 = sheet.createRow(i + 1 );
ArrayList rdata = (ArrayList)cdata.get(i);
// 打印一行数据
for ( int j = 0 ;j < rdata.size();j ++ )
{
HSSFCell cell = row1.createCell( ( short )j);
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
// 设置字符编码方式
cell.setEncoding(( short ) 1 );
Object o = rdata.get(j);
// 造型,使写入到表中的数值型对象恢复为数值型,
// 这样就可以进行运算了
if (o instanceof BigDecimal)
{
BigDecimal b = (BigDecimal)o;
cell.setCellValue(b.doubleValue());
}
else if (o instanceof Integer)
{
Integer it = (Integer)o;
cell.setCellValue(it.intValue());
}
else if (o instanceof Long)
{
Long l = (Long)o;
cell.setCellValue(l.intValue());
}
else if (o instanceof Double)
{
Double d = (Double)o;
cell.setCellValue(d.doubleValue());
}
else if (o instanceof Float)
{
Float f = (Float)o;
cell.setCellValue(f.floatValue());
}
else
{
cell.setCellValue(o + "" );
}
}
}
return workbook;
}
/** */ /**
* Just to test
* @param args String[]
*/
public static void main(String[] args)
{
ArrayList data = new ArrayList();
ArrayList header = new ArrayList();
header.add( " 学号 " );
header.add( " 姓名 " );
header.add( " 成绩 " );
for ( int i = 0 ;i < 3 ;i ++ )
{
ArrayList data1 = new ArrayList();
data1.add((i + 1 ) + "" );
data1.add( " Name " + (i + 1 ));
data1.add( "" + ( 80 + i));
data.add(data1);
}
ExcelModel model = new ExcelModel();
model.setPath( " E:/test.xls " );
model.setHeader(header);
model.setData(data);
ExcelOperator eo = new ExcelOperator();
try
{
eo.WriteExcel(model);
} catch (Exception e)
{
System.out.println(e.getMessage());
}
}
}
六、功能流程小结:
涉及到不同的项目,采取的框架与结构是可能不同的。我的实现方法可以应用到不同的项目中去,只是作为一个借鉴,它可能需要针对不同情况做相应调整与修改。
我的项目是采取了Spring+Struts+Hibernate的框架实现的,显示层仍然使用HTML、JSP文件,通过它传递客户端的请求,转到Action类调用业务逻辑对象实现相应功能。持久层使用了Hibernate,使用Hibernate作为数据持久层,在开发与维护方面都带来了较大的便利。至于相关框架的配置与实现,则不在本文论述的范围,请读者朋友参阅相关资料。我的项目中,使用了一个Service类(类似Manager的功能,编写接口与实现类,在Spring配置文件中加入,利用Spring的依赖注入技术在运行中取得对应的Bean实例......)来集成业务逻辑功能,通过它调用涉及到的数据访问类(DAO类),具体Dao类又利用Hibernate提供的对象进行数据库的查询或其他操作。这些东西,我就不再详述了,相信对这些技术使用得比我娴熟者大有人在。功能的最终实现,请看我在某个Action中的几行代码:
ExcelModel excel
=
new
ExcelModel();
excel.setSheetName(
"
BidCost
"
);
//
写入到Excel格式输出流供下载
try
{
// 调用自编的下载类,实现Excel文件的下载
ExcelDownLoad downLoad = new BidCostExcelDownLoad();
ExcelModel downExcel = downLoad.createDownLoadExcel(bidReportList,bcf,excel);
// 不生成文件,直接生成文件输出流供下载
downLoad.downLoad( " BidCost.xls " ,downExcel,response);
log.info( " create Excel outputStream successful! " );
}
catch
(Exception e)
{
msg.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
" bidding.cs.fileUpDown.fileDownError " )); // 文件下载失败!
saveErrors(request, msg);
log.info( " create Excel outputStream failed! " );
log.info(e.getMessage());
// e.printStackTrace();
return mapping.getInputForward();
}
请看客户端的显示页面:
贴子来自:http://www.blogjava.net/qnjian/archive/2005/12/30/25999.html