Servlet 实现文件上传

Servlet 实现文件上传

所谓文件上传就是将本地的文件发送到服务器中保存。例如我们向百度网盘中上传本地的资源或者我们将写好的博客上传到服务器等等就是典型的文件上传。

Servlet 3.0

上次完成文件下载功能使用的是 Servlet 2.5,但是想要完成文件上传,那么继续使用 Servlet 2.5 肯定不是一个好的选择,因此我们使用 Servlet 3.0 来完成文件上传。下面我来简单介绍一下 Servlet 3.0 的新特性:

  1. 新增的注解支持
    该版本新增了若干注解,用于简化 Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml 部署描述文件从该版本开始不再是必选的了。
  2. HttpServletRequest 对文件上传的支持
    此前,对于处理上传文件的操作一直是让开发者头疼的问题,因为 Servlet 本身没有对此提供直接的支持,需要使用第三方框架来实现,而且使用起来也不够简单。如今这都成为了历史,Servlet 3.0 已经提供了这个功能,而且使用也非常简单。

Servlet 3.0 的新特性当然肯定不止这些,但是其他的新特性在这里我们暂时还用不到,也就不做过多了解了。

必要条件

想要完成文件上传,肯定不是这么简单,它对浏览器端和服务器端都有许多的要求。

对浏览器的要求:

  1. 一个文件的大小一般肯定不止 1 KB,既然这样,那么要上传一个文件肯定不能使用 get 方式了,所以上传文件时必须采用 post 方式。
  2. 2.表单中必须有一个文件上传项 <input type="file">,而且必须有 name 属性。
  3. 必须设置表单的 enctype 属性值为 multipart/form-data

对服务器的要求:

  1. 当然,我们肯定得使用 Servlet 3.0。
  2. Servlet 3.0 中接收普通上传组件(除了文件上传组件)通过 request.getParameter(String)接收,而文件上传组件通过 request.getPart(String)接收。
  3. Servlet 3.0 要求服务器必须是 Tomcat7 及其以上。

准备工作

工欲善其事,必先利其器。

  1. 首先,打开 Eclipse,新建一个 Dynamic Web Project
  2. 键入项目名,选择运行时环境为 Apache Tomcat v7.0,选择 Servlet 版本为 3.0,然后点击 Finished
  3. 在项目的 WebContent 目录下,新建一个文件夹 upload,用来存放上传过来的文件。
  4. WebContent 目录下新建一个 index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>上传</title>
    </head>
    
    <body>
        <form action="${pageContext.request.contextPath}/UploadServlet" method="post" enctype="multipart/form-data">
            <label>选择一个文件:</label>
            <input type="file" name="file"><br>
            <input type="submit" value="上传"><br>
        </form>
    </body>
</html>
  1. 使用 Tomcat 将次项目发布,并在浏览器中预览。

    将服务器启动,然后在浏览器中输入:http://localhost:8080/upload

    好吧!样子有点丑,希望不要介意!如果出现以上界面,那么,准备工作就完成了!

完成案例

首先,新建一个 Servlet,在 Servlet 3.0 我们不必再为配置 web.xml 而烦恼了,只要要在 Servlet 的类名上面一行添加一个注解:

@WebServlet("/UploadServlet")

这个注解就相当与 Servlet 2.5 中的:

<servlet>
    <servlet-name>UploadServlet</servlet-name>
    <servlet-class>club.luckylight.upload.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UploadServlet</servlet-name>
    <url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>

这样比较,使用注解不是简便了很多。

然后,我们还需要添加另一个注解:

@MultipartConfig

该注解主要是为了辅助 Servlet 3.0 中 HttpServletRequest 提供的对上传文件的支持。该注解标注在 Servlet 上面,以表示该 Servlet 希望处理的请求的 MIME类型multipart/form-data

接下来,我们就需要根据上传组件的 name 属性获取它了。这里我们使用 Path request.getPart(String) 方法。

Part part = request.getPart("file");

然后,我们就需要根据 part 获取头信息,然后根据头信息获取文件的路径。

在浏览器抓包,获取头信息为:

据此,我们可以获取文件名或者文件路径。

String header = part.getHeader("content-disposition");
String path = header.substring(header.indexOf("filename=") + 10, header.length() - 1);

由于获取的有可能是文件名,也有可能是文件路径,为此,有必要编写一个工具类,用来获取文件的真实名称。

/**
 * 根据文件的路径获取文件真实名称
 * 
 * @param path
 *            文件的路径
 * @return 文件名称
 */
public static String getRealName(String path) {
    int index = path.lastIndexOf("\\");

    if (index == -1) {
        index = path.lastIndexOf("/");
    }

    return path.substring(index + 1);
}

然后,调用这个方法,获得文件名。

String name = UploadUtils.getRealName(path);

接下来,我们有必要,给每个文件分配一个存放目录,因此我又编写了一个方法,用来生成一个目录。

/**
 * 根据文件名返回一个目录
 * 
 * @param name
 *            文件名称
 * @return 目录
 */
public static String getDir(String name) {
    int i = name.hashCode();
    String hex = Integer.toHexString(i);
    int j = hex.length();

    for (int k = 0; k < 8 - j; k++) {
        hex = "0" + hex;
    }

    return "/" + hex.charAt(0) + "/" + hex.charAt(1);
}

到此,万事俱备,只欠东风。我们只需要将文件拷贝到服务器。

// 获取文件的真实路径
String realPath = this.getServletContext().getRealPath("/upload" + dir);
File file = new File(realPath);

if (!file.exists()) {
    file.mkdirs();
}

// 获取输入流
InputStream inputStream = part.getInputStream();
// 定义输出流
FileOutputStream outputStream = new FileOutputStream(new File(file, name));

// 从输入流中读入数据并写到输出字节流中
int len = -1;
byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes)) != -1) {
    outputStream.write(bytes, 0, len);
}

// 关闭资源
outputStream.close();
inputStream.close();

// 删除临时文件
part.delete();

下面来测试一下:

然后,在 Tomcatwebapps -> 项目名 -> upload 中就可以找到上传成功的文件了!

最后,我们打开音乐来试验下是否真的上传成功了?

嗯!薛之谦低沉的声音从耳机中传来,看来确实是上传成功了!

完整代码

UploadServlet.java

package club.luckylight.upload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import club.luckylight.util.UploadUtils;

@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {

    private static final long serialVersionUID = 5661013723204858883L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取文件上传组件
        Part part = request.getPart("file");

        // 获取文件的路径
        String header = part.getHeader("content-disposition");
        String path = header.substring(header.indexOf("filename=") + 10, header.length() - 1);

        // 获取文件名
        String name = UploadUtils.getRealName(path);

        // 获取文件的存放目录
        String dir = UploadUtils.getDir(name);

        String realPath = this.getServletContext().getRealPath("/upload" + dir);
        File file = new File(realPath);
        if (!file.exists()) {
            file.mkdirs();
        }

        // 对拷流
        InputStream inputStream = part.getInputStream();
        FileOutputStream outputStream = new FileOutputStream(new File(file, name));
        int len = -1;
        byte[] bytes = new byte[1024];
        while ((len = inputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, len);
        }

        // 关闭资源
        outputStream.close();
        inputStream.close();

        // 删除临时文件
        part.delete();

        response.setContentType("text/html;charset=utf-8");
        response.getWriter().print("文件" + name + "上传成功!");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

UploadUtils.java

package club.luckylight.util;

public class UploadUtils {

    /**
     * 根据文件的路径获取文件真实名称
     * 
     * @param path
     *            文件的路径
     * @return 文件名称
     */
    public static String getRealName(String path) {
        int index = path.lastIndexOf("\\");

        if (index == -1) {
            index = path.lastIndexOf("/");
        }

        return path.substring(index + 1);
    }

    /**
     * 根据文件名返回一个目录
     * 
     * @param name
     *            文件名称
     * @return 目录
     */
    public static String getDir(String name) {
        int i = name.hashCode();
        String hex = Integer.toHexString(i);
        int j = hex.length();

        for (int k = 0; k < 8 - j; k++) {
            hex = "0" + hex;
        }

        return "/" + hex.charAt(0) + "/" + hex.charAt(1);
    }
}

总结

这样,文件上传案例就完成了,希望大家喜欢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,358评论 6 343
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,014评论 11 349
  • 深吸气的训练方式:1.闻花香,2.半打哈气3.平躺法(腹部上放一本书)。 放松指引: 1.放松喉咙(吸气→打哈气8...
    紫兰chenyin阅读 126评论 0 0