用Apache POI导出Excel的改进
上文中使用的导出Excel方法提到上文的方法会导致同步操作的问题(即当某一个用户点击生成Excel链接后,执行生成"text.xls"后,正准备往外面输出文件,就在这时,另一用户又点击了同一个链接,这就导致了一个严重的问题,也就是当某一用户正要往外输出流的时候,另一个用户正准备创建这个流或者正要写这个流!),解决这个问题有两个方式:一是将临时文件命名为一个随机名字的文件,这会导致服务器的临时文件会增加(原方法是在程序中写死文件名,所以只会存在一个文件,这也是发生同步问题的根本原因),所以如果使用这个方法需要定期的删除这些文件,删除这些文件又有两种方式,一是在用户下载完成后再删除这些临时文件,另一个是写一个线程,隔一段时间后就将文件删除,后者无疑更好,因为很难去判断用户什么时候下载完成该文件,同时也要注意存在当用户已经下载但程序还没有来得及删除,服务器就已经停掉的情况,所以还要写一个Servlet在服务器启动的时候把这些没有来得及删除的文件删除掉;二是根本不生成临时文件,在导出Excel的程序中,直接将Apache poi中生成的WorkBook对象转换成InputStream流,这无疑是最好的方式
?
第一种方式:
1、JAVA Service代码:
public InputStream getInputStream() {HSSFWorkbook wk = new HSSFWorkbook();HSSFSheet sheet = wk.createSheet("UserList");HSSFRow row = sheet.createRow(0);HSSFCell cell = row.createCell((short)0);cell.setEncoding(HSSFCell.ENCODING_UTF_16);cell.setCellValue("序号");cell = row.createCell((short)1);cell.setEncoding(HSSFCell.ENCODING_UTF_16);cell.setCellValue("姓");cell = row.createCell((short)2);cell.setEncoding(HSSFCell.ENCODING_UTF_16);cell.setCellValue("名");cell = row.createCell((short)3);cell.setEncoding(HSSFCell.ENCODING_UTF_16);cell.setCellValue("年龄");List<User> userList = this.getUserList();for(int i=0;i<userList.size();++i){User user = userList.get(i);row = sheet.createRow(i+1);cell = row.createCell((short)0);cell.setEncoding(HSSFCell.ENCODING_UTF_16);cell.setCellValue(user.getId());cell = row.createCell((short)1);cell.setEncoding(HSSFCell.ENCODING_UTF_16);cell.setCellValue(user.getFirstname());cell = row.createCell((short)2);cell.setEncoding(HSSFCell.ENCODING_UTF_16);cell.setCellValue(user.getLastname());cell = row.createCell((short)3);cell.setEncoding(HSSFCell.ENCODING_UTF_16);cell.setCellValue(user.getAge());}////此处对文件进行了命名,但是下载时的文件默认名字实际上是struts.xml中配置的名字,////此文件是在tomcat服务器中bin目录中的临时文件的名字//File file = new File("test.xls");final File file = new File(new StringBuffer(StringUtil.getRandomString(10)).append(".xls").toString());try {OutputStream os = new FileOutputStream(file);wk.write(os);os.close();} catch (IOException e) {e.printStackTrace();}InputStream is=null;try {is = new FileInputStream(file);} catch (FileNotFoundException e) {e.printStackTrace();}new Thread(new Runnable() {public void run() {try {Thread.sleep(7000);//7秒钟} catch (InterruptedException e) {e.printStackTrace();}file.delete();}}).start();return is;}
?取得随机字符串除了自己写的外还可以使用common-lang包中的org.apache.commons.lang.RandomStringUtils来获得
2、Servlet的代码:
@SuppressWarnings("serial")public class DeleteExcelServlet extends HttpServlet {public void init() throws ServletException {//取得当前路径,当服务器的bin路径File file = new File(".");//第一种删除方法File[] fileList = file.listFiles();for(File f : fileList){if(f.getName().endsWith("xls")){f.delete();}}//第二种删除写法//File[] fileList = file.listFiles(new FileFilter() {//public boolean accept(File pathname) {//if(pathname.getName().endsWith("xls")){//return true;//}//return false;//}//});//for(File f : fileList){//f.delete();//}}}
?需在web.xml中配置load-on-startup属性,且无须配置url-mapping,因为无须用户访问同时也没有写doGet等方法
使用上面的方法在弹出下载提示框的时候,如果选择的不是下载,而是点击打开按钮,这时会在tomcat的bin目录产生两个Excel文件,一个是在对话框出来之前产生的,一个是在点击打开后产生的,而如果这时点的是保存,则不会再产生这个多余的文件,很明显这是跟我们的操作有关的,且产生了两条sql语句,说明service方法被调用了两次。造成这个问题的原因是因为跟浏览器碰到可下载链接时的处理方式有关,当浏览器遇到一个可下载的请求的时候,它有两种选择,一种是在当前浏览器将要下载的文件打开,另一种选择是弹出一个下载对话框,而根据上一往篇文章,我并没有显式配置它,所以struts使用的是默认的配置方式,即inline的方式,也就是第一种方式,这也就是导成这个问题的原因,将struts.xml文件修改如下:
<action name="generateExcel" method="generateExcel"><!-- 指定type为stream类型 --><result name="success" type="stream"><!-- 指定输出的为Excel文件 --><param name="contentType">application/vnd.ms-excel</param><!-- 指定下载时文件的默认名字,注貌似不能使用中文,同时filename不能写成fileName --><param name="contentDisposition">attachment;filename="AllUser.xls"</param><!-- 此处的值是对应于action中的某个返回值为InputStream的方法,如为下面的配置,那么 userAction中需要有一个public InputStream getDownloadFile()的方法 --><param name="inputName">downloadFile</param></result></action>
?在
<param name="contentDisposition">attachment;filename="AllUser.xls"</param>
中添加了attachment的属性
如果不声明,它会是这样的:
<param name="contentDisposition">inline;filename="AllUser.xls"</param>
改后仍有可能依然会出现两个xls文件,这有可能是迅雷的对浏览器下载的自动探测造成的,把它的这个功能关闭,即可发现成功。
?
?
?