快精灵印艺坊 您身边的文印专家
广州名片 深圳名片 会员卡 贵宾卡 印刷 设计教程
产品展示 在线订购 会员中心 产品模板 设计指南 在线编辑
 首页 名片设计   CorelDRAW   Illustrator   AuotoCAD   Painter   其他软件   Photoshop   Fireworks   Flash  

 » 彩色名片
 » PVC卡
 » 彩色磁性卡
 » 彩页/画册
 » 个性印务
 » 彩色不干胶
 » 明信片
   » 明信片
   » 彩色书签
   » 门挂
 » 其他产品与服务
   » 创业锦囊
   » 办公用品
     » 信封、信纸
     » 便签纸、斜面纸砖
     » 无碳复印纸
   » 海报
   » 大篇幅印刷
     » KT板
     » 海报
     » 横幅

用JAVABEAN实现文件上载

哀求分析
  要实现文件上载,我们必须先了解上载文件的HTTP哀求。下面这个简朴的应用示范了如何上载文件以及把HTTP哀求的原始数据写入文件。用文本编辑器查看该文件即可了解哀求的格式,在此基础上我们就可以提取出上载文件的名字、文件内容以及原本混合在一起的其他信息。

  这个简朴的应用是开发真正文件上载JavaBean的预备工作。它由三个文件构成:HTML文件main.html,JSP页面Jsp1.jsp,JavaBean文件SimpleBean.java。

  main.html提供一个表单,用户从这里选择文件并把文件上载到服务器。main.html的代码如下:
<html>
<head>
<title>文件上载</title>
</head>
<body>
<form action="jsp1.jsp" enctype="MULTIPART/FORM-DATA" method=post>
作者: <input type="text" name="author" />
<br />
公司: <input type="text" name="company" />
<br />
选择要上载的文件 <input type="file" name="filename" />
<br />
<input type="submit" value="上载" />
</form>
</body>
</html>

  可以看到,<form>标记有一个enctype属性,属性值是"MULTIPART/FORM-DATA"。包括提交按钮在内,表单里面共有4个输入元素。前面两个输入元素是普通的text元素,即author和company。第三个输入元素的type属性是file,这个输入元素就是用来选择文件的元素。

  表单的action属性值是Jsp1.jsp,这意味着哀求(包括上载的文件)将发送给Jsp1.jsp文件。Jsp1.jsp简朴地调用名为SimpleBean的JavaBean。
<jsp:useBean id="TheBean" scope="page" class="SimpleBean " />
<%
TheBean.doUpload(request);
%>

下面是SimpleBean的实现代码:
import java.io.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletInputStream;

public class FileUploadBean {
public void doUpload(HttpServletRequest request) throws
IOException {
PrintWriter pw = new PrintWriter(
new BufferedWriter(new FileWriter("Demo.out")));
ServletInputStream in = request.getInputStream();
int i = in.read();
while (i != -1) {
pw.print((char) i);
i = in.read();
}
pw.close();
}
}

这个JavaBean把HttpServletRequest对象的表单原始数据写入Demo.out文件。应用的用户界面由main.html文件提供,如下图所示。

  我们选择上载的文件是abisco.html。选择上载HTML文件是为了便于观察上载后的格式,因为HTML文件本质上是文本文件,我们可以方便地浏览其内容。abisco.html文件的内容如下:
<html>
<head>
<title>Abisco</title>
</head>
</html>
  点击“上载”按钮之后,表单就发送给了Jsp1.jsp文件,一起发送的还有abisco.html文件。Jsp1.jsp文件不会向浏览器发送任何应答内容,但它会生成一个Demo.out文件。
  打开Demo.out文件,我们可以看到如下内容:
-----------------------------7d15340138
Content-Disposition: form-data; name="Author"
A. Christie
-----------------------------7d15340138
Content-Disposition: form-data; name="Company"
Abisco
-----------------------------7d15340138
Content-Disposition: form-data; name="Filename"; filename="C:123dataabisco.html"
Content-Type: text/html
<html>
<head>
<title>Abisco</title>
</head>
</html>
-----------------------------7d15340138--
  可以看到,HTTP哀求体内包含了所有的表单输入,包括上载的文件。这些输入数据的分隔由一个分隔符实现。分隔符由一系列的“-”字符和一个随机数字构成。在上面的例子中,分隔符为“-----------------------------7d15340138”。最后一个分隔符结束哀求体,这个分隔符的后面多出两个“-”符号。

  对于非文件类型的输入数据,分隔符后面跟着下面这行内容:Content-Disposition: form-data; name=inputName。其中inputName是表单元素的名字。例如:Content-Disposition: form-data; name="Author"。在这行内容的后面,紧跟着两个连续的回车换行符和表单元素值。

  而对于文件型输入域,分隔符的后面有两行内容。第一行内容包含输入元素的名字以及上载文件在客户端的完整路径,如上例中这行内容是“Content-Disposition: form-data; name="Filename"; filename="C:123dataabisco.html"”。这行内容指出文件输入元素的名字是filename,文件的路径是“C:123dataabisco.html”。注重Windows浏览器会设置文件路径,而Unix/Linux以及Mac浏览器只发送文件名字。

  第二行包含了文件的内容类型,因此它的详细内容和上载的文件有关。本例中第二行的内容是“Content-Type: text/html”。

  和非文件输入元素相同,文件内容在两个连续的回车换行符之后正式开始。

上载文件
  众所周知,JavaBean是Java平台的软件组件,下面要实现的上载功能就是用JavaBean实现,所以它可以方便地应用到任何需要文件上载功能的应用之中。

代码清单如下:
package com.brainysoftware.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;
import java.util.Dictionary;
import java.util.Hashtable;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class FileUploadBean {
private String savePath, filepath, filename, contentType;
private Dictionary fields;

public String getFilename() {
return filename;
}

public String getFilepath() {
return filepath;
}

public void setSavePath(String savePath) {
this.savePath = savePath;
}

public String getContentType() {
return contentType;
}

public String getFieldValue(String fieldName) {
if (fields == null || fieldName == null)
return null;
return (String) fields.get(fieldName);
}

private void setFilename(String s) {
if (s==null)
return;
int pos = s.indexOf("filename="");
if (pos != -1) {
filepath = s.substring(pos+10, s.length()-1);
// Windows浏览器发送完整的文件路径和名字
// 但Linux/Unix和Mac浏览器只发送文件名字
pos = filepath.lastIndexOf("");
if (pos != -1)
filename = filepath.substring(pos + 1);
else
filename = filepath;
}
}

private void setContentType(String s) {
if (s==null)
return;
int pos = s.indexOf(": ");
if (pos != -1)
contentType = s.substring(pos+2, s.length());
}

public void doUpload(HttpServletRequest request) throws IOException {
ServletInputStream in = request.getInputStream();
byte[] line = new byte[128];
int i = in.readLine(line, 0, 128);
if (i < 3)
return;
int boundaryLength = i - 2;
String boundary = new String(line, 0, boundaryLength); //-2丢弃换行字符
fields = new Hashtable();

while (i != -1) {
String newLine = new String(line, 0, i);
if (newLine.startsWith("Content-Disposition: form-data; name="")) {
if (newLine.indexOf("filename="") != -1) {
setFilename(new String(line, 0, i-2));
if (filename==null)
return;

//文件内容
i = in.readLine(line, 0, 128);
setContentType(new String(line, 0, i-2));
i = in.readLine(line, 0, 128);

//空行
i = in.readLine(line, 0, 128);
newLine = new String(line, 0, i);
PrintWriter pw = new PrintWriter(new BufferedWriter(new
FileWriter((savePath==null? "" : savePath) + filename)));
while (i != -1 && !newLine.startsWith(boundary)) {
// 文件内容的最后一行包含换行字符

// 因此我们必须检查当前行是否是最

// 后一行
i = in.readLine(line, 0, 128);
if ((i==boundaryLength+2 || i==boundaryLength+4)
&& (new String(line, 0, i).startsWith(boundary)))
pw.print(newLine.substring(0, newLine.length()-2));
else
pw.print(newLine);
newLine = new String(line, 0, i);
}
pw.close();
}
else {
// 普通表单输入元素
// 获取输入元素名字
int pos = newLine.indexOf("name="");
String fieldName = newLine.substring(pos+6, newLine.length()-3);
i = in.readLine(line, 0, 128);
i = in.readLine(line, 0, 128);
newLine = new String(line, 0, i);
StringBuffer fieldValue = new StringBuffer(128);
while (i != -1 && !newLine.startsWith(boundary)) {
// 最后一行包含换行字符
// 因此我们必须检查当前行是否是最后一行
i = in.readLine(line, 0, 128);
if ((i==boundaryLength+2 || i==boundaryLength+4)
&& (new String(line, 0, i).startsWith(boundary)))
fieldValue.append(newLine.substring(0, newLine.length()-2));
else
fieldValue.append(newLine);
newLine = new String(line, 0, i);
}
fields.put(fieldName, fieldValue.toString());
}
}
i = in.readLine(line, 0, 128);
}
}
}
  代码的第一行是包声明,假如你不想让该类从属于任何包,可以删除这行代码。接下来的几行代码声明了该JavaBean所要引用的各个类和接口。
  FileUploadBean类有5个私有的属性(域),6个公用的方式,2个私有的方式。

属性
FileUploadBean类的5个域都是私有的,它们是:
private String savePath

该域指定了文件上载后保存到服务器的哪一个路径。savePath的值用setSavePath方式设置。这个值应该在调用doUpload方式之前设置;如没有设置,上载后的文件将保存到服务器的默认目录。

private String filepath
该域指定了上载文件在客户端的完整路径。filepath的值由doUpload方式设置,在JSP页面或者Servlet中调用getFilepath方式可以获取filepath域的值。对于非Windows下的浏览器,该值等于filename。

private String filename
该域是上载文件的名字。filename的值由setFilename方式设置。在JSP或者Servlet中调用getFilename方式可以获取filename域的值。

private String contentType
该域是上载文件的内容类型。contentType的值由doUpload方式设置,你可以用getContentType方式获得contentType域的值。

private Dictionary fields
fields域保存了用户在表单中输入数据的名字/值对。调用getFieldValue方式可以获取表单输入元素的值。

方式
  前面四个public类型的方式用于返回FileUploadBean对象的私有域,它们是:getFilepath,getFilename,getContentType以及getFieldValue。

public String getFilepath()
返回filepath私有域的值。

public String getFilename()
返回filename私有域的值。

public String getContentType()
返回contentType私有域的值。

public String getFieldValue(String fieldName)
返回HTML表单中指定输入元素的值,元素的名字通过fieldName参数指定。

public void setSavePath(String savePath)
用该方式指定服务器上保存上载文件的目录的名字。

public void doUpload(HttpServletRequest request) throws IOException
doUpload是FileUploadBean类中最重要的一个方式。它的任务有二个:第一,它从HTML表单提取出输入域的名字和值并保存到Dictionary对象;第二,doUpload方式提取出上载的文件,把这个文件保存到savePath指定的路径,并分别把文件的名字、路径、内容类型赋给filename、filepath和contentType域。

private void setContentType(String s)
由doUpload方式调用。setContentType方式从原始字节数据提取出上载文件的内容类型。

private void setFilename(String s)
由doUpload方式调用。setFilename方式从原始字节数据提取出文件路径和名字。
  doUpload方式的参数是Servlet/JSP容器创建的HttpServletRequest对象。HttpServletRequest对象描述了程序为了提取出HTML表单元素名字-值对以及上载文件必须处理的HTTP哀求。doUpload方式首先通过HttpServletRequest对象的getInputStream方式获得ServletInputStream对象。

  如前所述,每一个表单元素由分界符和一组回车换行符分隔。因此,我们可以一行一行地读入HttpServletRequest对象的内容。下面这行代码定义了一个名为line的byte数组:
byte[] line = new byte[128];

  然后,我们用ServletInputStream对象的readLine方式读入HttpServletRequest对象内容的第一行:
int i = in.readLine(line, 0, 128);

  第一行应该是分界符,而且假如没有错误的话,它的长度应该大于3。假如它的长度小于3,我们可以认为出现了错误,doUpload方式应该立刻返回:
if (i < 3)
return;

  分界符和分界符的长度都异常重要,从本文后面你可以看到这一点。分界符由一组回车换行符结束,因此它的实际长度要比readLine方式返回的字节数少2。

int boundaryLength = i - 2;
  丢弃byte数组line的最后2个回车换行符即可获得分界符:

String boundary = new String(line, 0, boundaryLength);
  接下来,fields域被实例化成Hashtable对象。这个Hashtable对象将用来保存HTML表单元素的名字/值对。

fields = new Hashtable();
  由于已经有了分界符,接下来我们就可以开始提取出表单元素的值。详细方式是用一个while循环按行读入HttpServletRequest对象的内容,直至碰到内容结束readLine方式返回-1为止。所有的表单元素都以分界符开始,后面跟上“Content-Disposition”行,这一行由下面这些字符开始:

Content-Disposition: form-data; name=
  表单元素有两种类型:文件,非文件(普通的表单元素,如TEXT或者HIDDEN元素)。这两种表单元素的区别在于文件元素包含字符串“filename="filename"”。由此,我们可以利用该信息把文件和非文件的表单输入元素区别开来,代码如下:
if (newLine.startsWith("Content-Disposition: form-data; name="")) {
if (newLine.indexOf("filename="") != -1) {

// 文件型表单输入元素
// 这里加上提取文件的代码
. . .
}
else {
// 普通表单输入元素
// 这里加上提取表单元素的代码
. . .
}
}

  现在,我们首先来看看提取文件内容的代码。
  文件路径包含在“Content-Disposition”的后面。为提取文件路径和文件名字,doUpload方式调用了setFilename私有方式。setFilename方式提取出文件路径和文件名字信息,然后把它们赋值给filepath和filename域。调用setFilename方式之后,filename域应该不再是null。假如此时filename域仍然是null,则说明碰到了问题,doUpload方式直接返回。

if (filename==null)
return;

  “Content-Disposition”行之后的下一行是内容类型行。因此,doUpload方式接着调用readLine方式,然后调用setContentType私有方式。setContentType方式和setFilename方式相似,它从原始字节数据中提取出上载文件的内容类型并保存到contentType域。

  紧接内容类型行的下一行是空行,因此程序再调用了一次readLine方式。

i = in.readLine(line, 0, 128);
  接下来开始了真正的文件内容。我们先应该做好通过PrintWriter对象把文件写入磁盘的预备。

PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(( savePath==null? "" : savePath ) + filename)));

  上载文件保存到哪个位置取决于savePath域是否已经设置。假如savePath域没有设置,它的值是null,则文件将被保存到默认目录;假如savePath域已经设置,它的值不是null,则上载的文件被保存到它所指定的目录。

  然后我们就可以提取文件的内容。详细方式是使用while循环,每次循环读入一行内容并通过PrintWriter的输出方式把它写入磁盘。但我们知道,文件的最后一行包含两个回车换行符号,所以保存到磁盘的字节数据不应该包含这两个字符。因此,假如读入的行不是文件的最后一行,我们把所有读到的字节数据写入磁盘;假如读入的行已经是文件的最后一行,写入磁盘的字节数据要减去最后两个字符。

  然而,我们并不知道文件的大小,我们只知道紧接文件内容的下一行又是一个分界符;或者,假如文件是最后一个HTML表单元素,接下来的一行是分界符加上两个短划线字符。因此,只要检查下一行内容是否是分界符,我们就知道了何时应该结束while循环。这就是前面说分界符很重要的原因,在这里我们必须用到分界符。

  虽然我们可以读取下一行内容然后用startsWith方式检查它是否是一个分界符,然而,由于字符串操作的开销异常大,为了减少字符串操作,我们比较readLine读入的字节数组的长度。后者应该等于boundaryLength + 2;或者,假如它是HttpServletRequest对象中的最后一行,由于多出了最后两个短划线字符,它应该等于boundaryLength + 4。由于一行内容即使不是分界符也可以和分界符相同长,当长度匹配之后我们又将它与分界符比较。这就是前面提到boundaryLength很重要的原因了。

  整个处理过程的实现代码如下:
while (i != -1 && !newLine.startsWith(boundary)) {
i = in.readLine(line, 0, 128);
if ((i==boundaryLength+2 || i==boundaryLength+4) && (new String(line, 0, i).startsWith(boundary)))
pw.print(newLine.substring(0, newLine.length()-2));
else pw.print(newLine);

newLine = new String(line, 0, i);
}

  把文件内容保存到磁盘之后,我们关闭了PrintWriter。
pw.close();
非文件的表单元素也可以用类似的方式提取。不同之处在于,此时我们不再把数据写入磁盘,而是把名字-值对保存到Dictionary对象。

fields.put(fieldName, fieldValue.toString());

应用实例
  编译好Bean之后,我们就可以从Servlet或者JSP页面中使用它了。可能你在Tomcat之类的Servlet/JSP环境下使用Bean,部署Bean最简朴的方式是把class文件压缩成jar文件,然而把jar文件放到Tomcat的lib目录下。要让Tomcat装入jar文件,你必须重新启动Tomcat。

  下面是一个HTML文件和一个JSP文件,它们示范了这个Bean的应用。HTML文件包含一个表单以及几个输入元素:
<html>
<head>
<title>文件上载</title>
</head>
<body>
<form action=jsp1.jsp enctype="MULTIPART/FORM-DATA" method=post>
作者: <input type=text name=author>
<br>
公司: <input type=text name=company>
<br>
说明: <textarea name=comment></textarea>
<br>
选择要上载的文件<input type=file name=filename>
<br>
文件描述: <input type=text name=description>
<br>
<input type=submit value="Upload">
</form>
</body>
</html>

  用户提交上述表单之后,该HTTP哀求将由Jsp1.jsp处理。Jsp1.jsp运用FileUpload这个Bean来处理哀求。Jsp1.jsp的代码如下:
<%@ page contentType="text/html;charset=gb2312"%>
<jsp:useBean id="TheBean" scope="page"
class="com.brainysoftware.web.FileUploadBean" />
<%
TheBean.doUpload(request);
out.println("Filename:" + TheBean.getFilename());
out.println("<BR>内容类型:" + TheBean.getContentType());
out.println("<BR>作者:" + TheBean.getFieldValue("Author"));
out.println("<BR>公司:" + TheBean.getFieldValue("Company"));
out.println("<BR>说明:" + TheBean.getFieldValue("Comment"));
%>  
返回类别: 教程
上一教程: 彻底解决J2ME中的中文问题
下一教程: 全面解析JDBC(六)

您可以阅读与"用JAVABEAN实现文件上载"相关的教程:
· 用JAVA SERVLET实现文件上载
· 使用JSF和MYFACES实现文件上载
· 用JSP文件上载轻松实现
· JAVABEAN实现多个文件上传的两种方式
· JavaBean实现多文件上传的两种方式
    微笑服务 优质保证 索取样品