与Java相伴的日子
相识,相知,相恋,到相守......我的日子因你的到来而充实,我的日子因你的存在而多姿!
posts - 4,comments - 31,trackbacks - 0
  我做过的一个项目,需要实现在线实时生成 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();                             

                           }
                       


         请看客户端的显示页面:

         

 
  (声明:此文为原创,转载请注明出处及链接网址!)Last modified on 2006年4月15日 13:37:17

 

posted on 2005-12-30 10:08 南一郎 阅读(12257) 评论(16)  编辑  收藏

FeedBack:
# re: 在线实时生成Excel文件流供下载
2006-09-14 16:46 | junglesong的博客
覺得這樣過于復雜了,没有必要.  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2007-04-23 14:13 | Koala
我看到这个文章的时间比较晚了,问个问题,可以回答下吗?
BaseExcelDownLoad 里面的download方法getOutputStream()方法,获得输出流,XXjsp的生成的类XX_jsp.java首先也要获得输出流。就会出现
java.lang.IllegalStateException: getOutputStream() has already been called for this response
是怎么解决这个问题的  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2007-04-23 14:16 | Koala
有办法可以给我发个解决方案吗?
woleve@163.com  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2007-11-12 17:07 | pirate
请问可以下载到你的源码吗?  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2008-01-06 12:18 | smeg
虽然我也在学java,但是才刚开始学。你说的我都不大懂。



~~~~~~~~~~~有空再过来  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2008-01-30 14:54 | javaing
学习了,我现在是用的JSP和servlet开发的,要求是当单击页面上的一个按钮把页面表格的数据以Excel格式导出,不知道你的方法能不能解决  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2008-06-02 15:38 | run
感谢楼主的分享,我的问题已经解决。不过有一个问题想问一下楼主,你的第一个download方法是不是实现按照写好的一个Excle模板生成数据啊?(这个Excle模板是只有header头名称和一些可以利用excle自身函数可以计算的定义)?如果不是,请问这个怎么实现?  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2008-06-13 15:34 | 樱花季节
BaseExcelDownLoad 里面的download方法getOutputStream()方法,获得输出流,XXjsp的生成的类XX_jsp.java首先也要获得输出流。就会出现
java.lang.IllegalStateException: getOutputStream() has already been called for this response

我跟
Koala一样也遇到这个问题,可以解答一下吗?
  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2008-06-16 10:26 | Author
可能是因为我是使用Servlet或Action类来处理,你们是直接用JSP.出现的原因,可能是重复获取输出流。用JSp的话,可能要适当改变一下。共用一个outputStream.  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2008-06-16 10:28 | Author
Excel Model只是自己写的一个类,因为我目前需要的只是标题和Excel表里的数据内容,所以暂时只有这些属性。你也可以有自己的更好的实现。  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载[未登录]
2009-09-25 13:04 | tomcat
不错--好文  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载[未登录]
2010-11-29 11:44 | 晨曦
response.flushBuffer();//强行将响应缓存中的内容发送到目的地 我恰恰是这句代码报了异常。  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2013-09-22 15:53 | sss
@junglesong的博客
  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载
2014-02-26 20:59 | 程浩
你好,请问使用这个代码导出的excel,单元格格式设置的String 没有生效,是为什么?  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载[未登录]
2015-02-27 14:01 | carrie
很好,改改controller就可以了,帮了大忙了,多谢  回复  更多评论
  
# re: 在线实时生成Excel文件流供下载[未登录]
2016-05-26 14:17 | 啊啊
啊啊啊  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: