如何使用JSF 2.2 <h:inputFile>上传文件?保存的文件在哪里?

18

我希望能在我的JSF2.2 Web应用程序中上传文件,因此我开始使用新的<h:inputFile>组件。

我唯一的问题是如何指定保存文件的位置在服务器上的位置?我想把它们作为java.io.File实例获取。这必须在后端bean中实现,但我不太清楚该如何操作。


1
将h:inputFile的值绑定到bean中的Part属性。然后,您的操作方法可以使用part.write(fileName)将数据保存在任何您喜欢的地方。 - VGR
1个回答

43
JSF不会将文件保存在任何预定义的位置。它基本上只会以javax.servlet.http.Part实例的形式向您提供上传的文件,该实例在服务器的内存和/或临时磁盘存储位置中被临时存储,您不必担心这些。
重要的是,当bean操作(监听器)方法被调用时,您需要尽快读取Part。当与HTTP请求关联的HTTP响应完成时,临时存储可能会被清除。换句话说,在随后的请求中,上传的文件不一定可用。
因此,假设有一个
<h:form enctype="multipart/form-data">
    <h:inputFile value="#{bean.uploadedFile}">
        <f:ajax listener="#{bean.upload}" />
    </h:inputFile>
</h:form>

您基本上有两个选项来保存它:
1. 将所有原始文件内容读入byte[]
您可以使用InputStream#readAllBytes()来实现。
private Part uploadedFile; // +getter+setter
private String fileName;
private byte[] fileContents;

public void upload() {
    fileName = Paths.get(uploadedFile.getSubmittedFileName()).getFileName().toString(); // MSIE fix.

    try (InputStream input = uploadedFile.getInputStream()) {
        fileContents = input.readAllBytes();
    }
    catch (IOException e) {
        // Show faces message?
    }
}

请注意Path#getFileName()。这是一个解决MSIE获取提交文件名的方法。该浏览器不正确地发送完整的文件路径,而不仅仅是文件名。

如果您还没有使用Java 9,因此无法使用InputStream#readAllBytes(),那么请前往Convert InputStream to byte array in Java查看将InputStream转换为byte[]的其他方法。

请记住,上传文件的每个字节都会消耗一字节的服务器内存。当用户经常这样做或可以轻松滥用您的系统时,请小心您的服务器不要耗尽内存。如果想避免这种情况,最好使用本地磁盘文件系统上的(临时)文件。

2. 或者,将其写入本地磁盘文件系统

为了将其保存到所需位置,您需要通过Part#getInputStream()获取内容,然后将其copy到代表位置的Path中。
private Part uploadedFile; // +getter+setter
private File savedFile;

public void upload() {
    String fileName = Paths.get(uploadedFile.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
    savedFile = new File(uploads, fileName);

    try (InputStream input = file.getInputStream()) {
        Files.copy(input, savedFile.toPath());
    }
    catch (IOException e) {
        // Show faces message?
    }
}

请注意Path#getFileName()。这是一个针对MSIE的修复,用于获取提交的文件名。该浏览器错误地发送完整的文件路径,而不仅仅是文件名。 uploads文件夹和filename完全由您控制。例如:"/path/to/uploads"Part#getSubmittedFileName()。请记住,任何现有的文件都将被覆盖,您可能需要使用File#createTempFile()来自动生成文件名。您可以在this answer中找到一个详细的例子。
请勿使用Part#write(),因为一些人可能会建议这样做。它基本上会将文件重命名为由@MultipartConfig(location)标识的临时存储位置中的文件。也不要使用ExternalContext#getRealPath()将上传的文件保存在部署文件夹中。当WAR重新部署时,文件将会丢失,因为该文件并未包含在原始WAR中。请始终将其保存在部署文件夹之外的绝对路径上,以确保文件不会丢失。
要查看上传和预览功能的实时演示,请查看OmniFaces展示页面上<o:inputFile>页面的演示部分
另请参阅:

这里的f:ajax示例中的事件是什么?是onClick吗? - Koray Tugay
只使用默认设置。答案保持原样。 - BalusC
是的,我的意思是默认值是什么? - Koray Tugay

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