Servlet3.0提供了javax.servlet.http.Part
类,通过它可以很方便的上传文件。
Part类介绍
javax.servlet.http.Part
是Servlet3.0中新增加的API之一。每个Part对象表示一个文件上传域,该对象提供了许多方法来获得上传文件的类型、大小、输入流等,还提供了一个write方法将文件写入服务器磁盘。
Part类的常用方法有:
- getHeaderNames:获得用户请求头中的MIME值名称;
- getContentType:获得上传文件的类型;
- getSize:获得上传文件的大小;
- getSubmittedFileName:获得上传文件在客户端的绝对路径;
- write:将上传文件写入服务器磁盘。
使用Servlet上传文件
Servlet
先来看我们的程序,首先是处理文件上传的Servlet类:
// UploadServlet.java
@WebServlet(
urlPatterns = { "/upload" },
initParams = {
@WebInitParam(name = "basepath", value = "F:\\workspace\\javaEE\\WebDemo\\res\\")
},
asyncSupported = true)
@MultipartConfig
public class UploadServlet extends HttpServlet {
private String basePath;
private static final long serialVersionUID = 1L;
public void init(ServletConfig config) throws ServletException {
basePath=config.getInitParameter("basepath");
}
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
AsyncContext ac=request.startAsync(request, response);
ac.setTimeout(10*60*1000);
ac.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent event) throws IOException {
AsyncContext ac=event.getAsyncContext();
ac.getResponse().getWriter().println("文件上传成功!<br>");
ac.dispatch("/upload");
}
@Override
public void onError(AsyncEvent event) throws IOException {
event.getAsyncContext().getResponse().getWriter().println("文件上传出错!<br>");
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
event.getAsyncContext().getResponse().getWriter().println("开始上传文件...<br>");
}
@Override
public void onTimeout(AsyncEvent event) throws IOException {
event.getAsyncContext().getResponse().getWriter().println("文件上传超时!<br>");
}
});
ac.start(new UploadTask(ac, basePath));
}
public static class UploadTask implements Runnable {
private AsyncContext ac;
private String basePath;
public UploadTask(AsyncContext a, String bp) {
ac=a;
basePath=bp;
}
@Override
public void run() {
HttpServletRequest request=(HttpServletRequest) ac.getRequest();
HttpServletResponse response=(HttpServletResponse) ac.getResponse();
response.setContentType("text/html;charset=GBK");
PrintWriter out;
try {
out = response.getWriter();
Part part=request.getPart("file");
// 文件上传头
Collection<String> headers=part.getHeaderNames();
// 文件类型
String fileType=part.getContentType();
// 文件大小
long fileSize=part.getSize();
// 客户端上传的文件名
String submitName=part.getSubmittedFileName();
// 服务器保存的文件名
String fileName=UUID.randomUUID().toString()+
submitName.substring(submitName.lastIndexOf('.'));
out.println("HEADER: "+"<br>");
for(String header : headers)
out.println(header+"="+part.getHeader(header)+"<br>");
out.println("文件类型: "+fileType+"<br>");
out.println("文件大小: "+fileSize+"<br>");
out.println("客户端文件名: "+submitName+"<br>");
out.println("服务端文件名: "+fileName+"<br>");
if(fileType.startsWith("image"))
part.write(basePath+"image\\"+fileName);
else if(fileType.startsWith("audio"))
part.write(basePath+"audio\\"+fileName);
else if(fileType.startsWith("video"))
part.write(basePath+"video\\"+fileName);
else
part.write(basePath+"file\\"+fileName);
ac.complete();
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
}
}
}
由于上传文件是一个耗时的工作,所以我们使用异步处理来上传文件,关于异步处理的内容可以参考我的另一篇文章:《JavaEE Servlet的异步处理》。
在UploadTask中进行实际的文件上传工作。request.getPart
方法可以获得文件上传域对象,我们通过调用它的一系列方法输出了文件信息,最后调用write方法将其写入磁盘。
另外还要注意,如果想要上传文件,Servlet需要使用@MultipartConfig
注解配置。
jsp页面
下面还需要一个jsp页面来选择要上传的文件:
<!-- upload.jsp -->
<body>
<form method="post" action="upload" enctype="multipart/form-data">
选择文件: <input type="file" id="file" name="file"><br>
<input type="submit" value="上传"><br>
</form>
</body>
form表单中有几个需要注意的地方:
1. 多了一个enctype
属性,它指定表单数据的编码方式,该属性有如下三个值:
- application/x-www-form-urlencoded:这是默认的方式,此方式只会发送文件域的文本框中的字符串,即用户选择文件的绝对路径;
- multipart/form-data:此方式以二进制流处理表单数据,这种方式会发送文件域指定文件的内容;
- text/plain:此方式当表单的action属性为mailto:URL
时比较方便,主要适用于直接通过表单发送邮件的方式。
2. 有一个类型为file
的input,它就是文件上传域,它用于选择要上传的文件。
上传文件
现在我们开始上传文件,运行Web应用,选择upload.jsp
页面,如下所示:
然后我们随便选择一个文件,点击上传,结果如下(如果文件较大,可能需要等待较长的时间):
可以看到,这里上传的文件在客户端和服务端具有不同的名字。这是因为如果有多位用户上传文件,他们上传的文件难免会有相同的文件名,所以我们使用java.util.UUID
类来产生新的文件名。
UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。按照开放软件基金会(OSF)制定的标准计算方法,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。由以下几部分组合而成:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,经过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡则以其他方式获得),UUID的唯一缺陷在于生成的结果串会比较长。
源码
上述源代码已经上传到github:
https://github.com/jzyhywxz/WebDemo