如何通过Java EE Web应用程序将文件存储在服务器(Web容器)上?

12

我开发了一个Java EE网络应用程序。该应用程序允许用户通过浏览器上传文件。一旦用户上传了文件,该应用程序首先将上传的文件存储在服务器上(它正在运行的服务器上),然后对其进行处理。

目前,我将文件存储在服务器上的方法如下:

try {
    // formFile represents the uploaded file
    FormFile formFile = programForm.getTheFile();
    String path = getServlet().getServletContext().getRealPath("") + "/"
        + formFile.getFileName();
    System.out.println(path);
    file = new File(path);
    outputStream = new FileOutputStream(file);
    outputStream.write(formFile.getFileData());
}

其中,formFile 表示上传的文件。

现在的问题是,在一些服务器上它可以正常运行,但在另一些服务器上 getServlet().getServletContext().getRealPath("") 返回了 null,所以我得到的最终路径是 null/filename,文件没有保存在服务器上。

当我查看 ServletContext.getRealPath() 方法的 API 时,我发现如下内容:

public java.lang.String getRealPath(java.lang.String path)

返回一个字符串,其中包含给定虚拟路径的实际路径。例如,路径 "/index.html" 在服务器文件系统上的绝对路径将由请求 "http://host/contextPath/index.html" 提供,其中 contextPath 是该 ServletContext 的上下文路径。

返回的实际路径将采用计算机和操作系统所需的格式,包括适当的路径分隔符。如果 Servlet 容器无法将虚拟路径转换为实际路径,该方法将返回 null(例如,当内容从 .war 存档中提供时)。

那么,是否有其他方法可以在那些返回 null 的服务器上存储文件,而不是使用 getServlet().getServletContext().getRealPath("") 方法呢?

2个回答

21
按照规范,Servlet 容器保证提供的唯一“真实”路径是临时目录。您可以通过 ServletContext.getAttribute("javax.servlet.context.tempdir") 获取该目录。但是,这些文件对 Web 上下文不可见(即您无法发布简单 URL 来提供这些文件),而且这些文件在 Web 应用程序或服务器重启后没有任何保障。
如果您只需要一个短时间存储工作文件的地方,那么这会很好用。
如果您确实需要一个目录,您可以将其设为配置参数(可以是环境变量、Java 属性(即 java -Dyour.file.here=/tmp/files ...)、在 web.xml 中设置的上下文参数、通过 Web 表单存储在数据库中的配置参数等)。然后由部署者为您设置此目录。
但是,如果您需要实际上稍后提供该文件,您将需要使用容器特定机制将外部目录“挂载”到您的 Web 应用程序中(例如 Glassfish 作为“备用文档根目录”,其他类似概念),或者您将需要编写一个 Servlet/Filter,以便提供 Web 应用程序之外的文件存储服务。这个FileServlet非常完整,如您所见,创建自己的 Servlet/Filter 虽然不难,但要做好却并不简单。 编辑: 基本意思相同,但不要使用“getRealPath”,而是使用“getInitParameter”。
所以:
String filePath = getServletContext().getInitParameter("storedFilePath") + "/" + fileName;

走得好。

再次编辑:

至于路径的内容,我会给它一个绝对路径。否则,你需要知道应用服务器在执行期间设置其默认路径的位置,而每个应用服务器可能会使用不同的目录。例如,我相信Glassfish的工作目录是正在运行的域的config目录。并不是一个特别明显的选择。

因此,一定要使用绝对路径。这样,你就知道文件将放到哪里,并且如果必要的话,你可以在操作系统级别控制该目录的访问权限。


我需要将文件存储在一个能够在服务器和Web应用程序重新启动后继续存在的目录中。根据您的答复,我想通过设置上下文参数将其作为配置参数。但我不知道如何使用它来存储文件。您能否提供一个示例或参考示例? - Amit
@Will 你向我展示了如何检索上下文参数的值,这是我已经知道的。也许我的评论没有表达清楚。实际上,我的意思是你会给“storedFilePath”上下文参数赋什么值。相对文件路径还是绝对文件路径? - Amit
如果您提供了相对路径,那么如何将其转换为绝对路径? - Amit
请注意,直接访问文件系统将使您依赖于特定的供应商,并可能失败 - 特别是在多主机应用服务器上。 - Thorbjørn Ravn Andersen
谢谢Will Hartung,您的建议对我的需求非常有用。 - Sundhar

13

从Java EE容器向文件系统写入数据并不是一个推荐的做法,特别是如果你需要处理所写入的数据:

  • 它不具有事务性
  • 它会影响可移植性(如果你处于集群环境中怎么办)
  • 它需要设置目标位置的外部参数

如果可以选择的话,我建议将文件存储在数据库中或使用JCR存储库(例如Jackrabbit)。


1
@Yatendra 我在强调“记录”文件系统写入的缺点。对于许多应用程序来说,这可能根本不是一个选项,但对您来说可能不是问题。现在,回答您的问题... 1)可以在数据库中进行写入(在LOB中);虽然不是理想的选择,但如果您每天不写入数千个文件,那么应该没问题。2)Jackrabbit是JCR API(JSR-170)的实现,这是一种标准API,用于编写和存储文件到内容存储库,并因此成为一种替代方案。但是,对于小型应用程序来说,使用JCR存储库似乎有些过度。会使用数据库。 - Pascal Thivent
我有一个类似的问题,但我需要存储小文件 - 因此使用任何需要单独设置和支持的数据库都是一个很大的开销。因此,对于这个答案投反对票,并为@Will Hartung的答案投赞成票 - 因为对于简单的问题来说,它看起来更加简单。 - Vladimir
1
@Vladimir,你因为不想使用数据库而_downvote_一个正确的答案?祝你在微妙的供应商依赖方面好运。 - Thorbjørn Ravn Andersen
1
@ThorbjørnRavnAndersen,为什么不可以?这是民主,对吧?我可以进行投反对票,我觉得回答是不正确的——即使问题不是关于如何组织文件上传的,而是有关技术细节。:-) 我没有依赖关系。我认为在数据库中存储文件是不自然的,这就是为什么文件系统存在的原因。特别是在那些更优雅地将上传的文件存储在磁盘上的框架中,我会避免使用框架。 - Vladimir
@ThorbjørnRavnAndersen,还要注意另一个答案得票更高。这意味着正确的答案只对原始用户正确 :-) - Vladimir
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接