首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

Java 文件下传和上载组件的设计与实现

2012-12-26 
Java 文件上传和下载组件的设计与实现概 述  文件上传和下载是 Web 应用中的一个常见功能,相信各位或多或

Java 文件上传和下载组件的设计与实现

概 述

  文件上传和下载是 Web 应用中的一个常见功能,相信各位或多或少都曾写过这方面相关的代码。但本座看过不少人在实现上传或下载功能时总是不知不觉间与程序的业务逻辑纠缠在一起,因此,当其他地方要用到这些功能时则无可避免地 Copy / Pase,然后再进行修改。这样丑陋不堪的做法导致非常容易出错不说,更大的问题是严重浪费时间不断做重复类似的工作,这是本座绝不能容忍的。哎,人生苦短啊,浪费时间在这些重复工作身上实在是不值得,何不把这些时间省出来打几盘罗马或者踢一场球?为此,本座利用一些闲暇之时光编写了一个通用的文件上传和文件下载组件,实现方法纯粹是基于 JSP,没有太高的技术难度,总之老少咸宜 ^_^。现把设计的思路和实现的方法向各位娓娓道来,希望能起到抛砖引玉的效果,激发大家的创造性思维。

  任何公共组件的设计都必须考虑下面两个问题:

  一、如何才能重用?

  答:首先,重用的组件必须有用,也就是说,是功能完备的。但另一方面,如果组件负责的职能太多也会影响重用。试想,一个文件上传组件同时还要负责插入数据库记录,一个文件下载组件还要负责解析下载请求并查找要下载的文件那可真是太悲哀了,叫别人如何重用?也就是说,重用组件必须是功能完备并职责单一的,绝对不能越界参与应用业务逻辑处理,不在其位不谋其政。

  另外,要重用的组件绝不能反向依赖于上层使用者。通常,重用组件以接口或类的形式提供给上层使用者调用,并放在单独的包中。也就是说,上层使用者所在的包依赖于组件所在的包,如果组件再反向依赖于上层使用者,则两个包之间就存在依赖环,严重违反了面向对象设计原则中的无环依赖原则(若想了解更多关于设计原则的内容请猛击这里 ^_^)。试想,通用的文件上传或下载组件如果需要读取一个在应用程序的某个地方定义的字符串常量来确定文件上传或下载的目录,那是囧了。

  本文件上传和下载组件在设计时充分考虑到上述问题,只负责单纯的上传和下载工作,所需要的外部信息均通过相应方法进行设置,不会依赖于任何使用者。

?

  二、组件的可用性如何?

  答:这是一个相当有深度的问题 ^_^ 所谓可用性通俗来说就是好不好用,使用起来是否方便。作为公共组件来说,可用性是一个十分重要的质量指标。如果组件十分复杂难用,倒不如自己花点时间自己写一个来得舒坦。本文件上传和下载组件在设计的过程中十分注重可用性目标,操作组件的代码行数不超过 10 行,只需几个步骤:

    1. 生成组件实例设置实例属性调用上传/下载方法处理调用结果

  水吹得已经够多了,下面让我们来看看文件上传和下载组件分别是如何实现的。


文件上传

  文件上传操作通常会附加一些限制,如:文件类型、上传文件总大小、每个文件的最大大小等。除此以外,作为一个通用组件还需要考虑更多的问题,如:支持自定义文件保存目录、支持相对路径和绝对路径、支持自定义保存的文件的文件名称、支持上传进度反馈和上传失败清理等。另外,本座也不想重新造车轮,本组件是基于 Commons File Upload 实现,省却了本座大量的工作 ^_^ 下面先从一个具体的使用例子讲起:

  • 上传请求界面及代码

    ?

    Java 文件下传和上载组件的设计与实现

    ?

    ="gender" value="false"> &nbsp;女 <input type="radio"" name="gender" value="true" checked="checked"> <br> Working age: <select name="workingAge"> <option value="-1">-请选择-</option> <option value="3">三年</option> <option value="5" selected="selected">五年</option> <option value="10">十年</option> <option value="20">二十年</option> </select> <br> Interest: 游泳 <input type="checkbox" name="interest" value="1" checked="checked"> &nbsp;打球 <input type="checkbox" name="interest" value="2" checked="checked"> &nbsp;下棋 <input type="checkbox" name="interest" value="3"> &nbsp;打麻将 <input type="checkbox" name="interest" value="4"> &nbsp;看书 <input type="checkbox" name="interest" value="5" checked="checked"> <br> Photo 1.1: <input type="file"" name="photo-1"> <br> Photo 1.2: <input type="file"" name="photo-1"> <br> Photo 2.1: <input type="file"" name="photo-2"> <br> <br> <input type="submit" value="确 定">&nbsp;&nbsp;<input type="reset" value="重 置"></form>

      从上面的 HTML 代码可以看出,表单有 6 个普通域和 3 个文件域,其中前两个文件域的 name 属性相同。

      上传处理代码

      // 确定文件的读取范围(用于断点续传) if(range != null) { if(range.getBegin() != null) { begin = range.getBegin(); if(range.getEnd() != null) end = range.getEnd(); } else { if(range.getEnd() != null) begin = end + range.getEnd() + 1; } String contentRange = String.format("bytes %d-%d/%d", begin, end, length); response.setHeader("Accept-Ranges", "bytes"); response.setHeader("Content-Range", contentRange); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); } // 实际执行下载操作 doDownloadFile(response, file, begin, end); } // 实际执行下载操作 private void doDownloadFile(HttpServletResponse response, File file, int begin, int end) throws IOException { InputStream is = null; OutputStream os = null; try { byte[] b = new byte[bufferSize]; is = new BufferedInputStream(new FileInputStream(file)); os = new BufferedOutputStream(response.getOutputStream()); // 跳过已下载的文件内容 is.skip(begin); // I/O 读写 for(int i, left = end - begin + 1; left > 0 && ((i = is.read(b, 0, Math.min(b.length, left))) != -1); left -= i) os.write(b, 0, i); os.flush(); } finally { if(is != null) {try{is.close();} catch(IOException e) {}} if(os != null) {try{os.close();} catch(IOException e) {}} } } /** 文件下载结果枚举值 */ public static enum Result { /** 成功 */ SUCCESS, /** 失败:文件不存在 */ FILE_NOT_FOUND, /** 失败:读写操作失败 */ READ_WRITE_FAIL, /** 失败:未知异常 */ UNKNOWN_EXCEPTION; }}

      ?

热点排行