Java Servlet:删除临时文件

3
我正在开发一个Java Servlet,它会创建一个临时文件用于会话。当用户“退出”时,我需要删除临时文件,并将用户重定向到初始(“登录”)页面。重定向功能正常,但是临时文件仍然存在。
我猜测这可能与文件路径有关,但我并不确定。我在会话初始化时创建了该文件:
String path = request.getSession().getServletContext().getRealPath("/");
File file = File.createTempFile("getter", ".html", new File(path + "/tmp/"));

然后,在关闭会话时,我执行以下操作:

file.delete();

我知道关于file.deleteOnExit(),但是...我何时退出Servlet呢?也许我有点困惑,但是我确实需要帮助!:)

提前感谢你的帮助!

编辑

那么,下面提供一些详细信息:

我正在使用一个Servlet,目前还没有处理会话。我同意@Joop的观点,需要实现会话管理,但目前只想进行一些简单的测试。

所以,我的Servlet处理GETPOST请求。我在POST请求中使用一个标志调用内部函数,该内部函数将文件(在类中声明为private File file;)实例化为一个新的临时文件。在后续调用中,文件被填充并保存。在用户看到的页面中,我有一个指向Servlet的锚点(即'this'),传递一个标志作为参数,该标志表示“注销”。然后我调用另一个内部函数来删除先前实例化的文件。

如果这是会话问题,我将实现管理器并发布我的发现。

编辑2

我实现了一个HttpSessionListener,一切似乎都正常。现在,在创建会话时,我在之前声明的目录中实例化一个文件(请注意,这不是临时文件,我使用File file = new File(path + "/tmp/" + req.getSession().getId() + ".html");,因此文件名等于会话ID)。然后,我向会话添加一个属性,其值是文件的完整路径。我继续像往常一样填充我的文件,当用户选择注销时,我使会话无效。然后,在监听器内部,我检索文件的路径,因此可以获取指向它的指针:

String fname = ev.getSession().getAttribute("filename").toString();
File f = new File(fname);
f.delete();

现在我收到的消息是积极的,也就是说f.delete()返回true,然后我执行f.exists(),得到false。所以应该没问题了。然而,这些文件在物理上存在,也就是它们仍然存在于磁盘上。

我可以尝试@A4L友好提供的示例。我做错了什么吗..?


你在哪里看到文件仍然存在?你使用文件资源管理器还是在IDE项目中看到它(例如在Eclipse中,您需要刷新项目,然后文件将从视图中消失)。你确定该文件不是来自以前运行的僵尸文件,可能删除还没有生效吗? - A4L
是的,我忘了提到这一点。正如我在另一个评论中所说,文件输出的完整名称在各个地方都有。因此,在创建文件时,我得到:Created file: F:\workspace\eclipse\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\JavaTestServlet\tmp\241B5CA17233009D92AAA46E1E378BAE.html 然后,在删除文件后,我检查 file.exists();,结果为:F:\workspace\eclipse\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\JavaTestServlet\tmp\241B5CA17233009D92AAA46E1E378BAE.html exists: false 但是该文件可以从文件浏览器中看到... - Iordan Iordanov
我尝试在删除文件之前添加检查文件是否存在的步骤。因此,我会得到文件存在的结果,然后成功删除它,最后文件不存在了。这是可以的。只是文件仍然留存在文件系统中... - Iordan Iordanov
这很奇怪:S...从我看到的,你在eclipse中将项目设置为“动态Web项目”。在这种情况下,eclipse管理您的Web内容目录,该目录位于工作区的“.metadata”目录中的某个位置。也许eclipse在那里做了一些奇怪的事情。您应该尝试手动将项目部署到独立的tomcat中(不使用eclipse),并查看文件是否在删除后仍然存在。 - A4L
正如我在@BalusC的帖子中所评论的,我可能能够消除临时文件。在上一次尝试中,我甚至无法创建临时文件,因此我认为在没有它们的情况下工作会更好。非常感谢您的建议,它非常宝贵 :) - Iordan Iordanov
4个回答

9
请停止在运行时向部署文件夹写入任意文件。相反,请将其写入真正的临时文件夹中。完全摒弃以下行:
path = request.getSession().getServletContext().getRealPath("/");

只需使用

File file = File.createTempFile("getter", ".html");

你的具体问题很可能是由于部署文件夹中的文件通常被servlet容器锁定而引起的。你无法删除其中的文件。
一个提示:每当你认为getRealPath()可以解决问题时,你应该立即停止编写代码,并仔细考虑它是否是修复具体问题的正确工具。在我开发基于Servlet的Web应用程序的十年中,没有一个合理的真实世界用例需要使用该方法。另请参见What does servletcontext.getRealPath("/") mean and when should I use it
我知道file.deleteOnExit(),但是...什么时候退出Servlet呢? 你不需要退出。容器会退出。这里的“退出”基本上意味着整个JVM的关闭。这甚至在javadoc中文字上提到了(重点是我的)。
请求删除此抽象路径名表示的文件或目录虚拟机终止时

谢谢您的见解 :) 有经验的人给你提示总是很好的。我想我将能够完全消除使用临时文件,也许它甚至会工作得更好。我会查看您提供的文章以备将来参考 :) - Iordan Iordanov
最好在Web应用程序自己的临时目录((File)servletContext.getAttribute(ServletContext.TEMPDIR))中创建临时文件。 - Ian Roberts
@Ian:File#createTempFile() 使用的是 java.io.tmpdir,该值已经由容器正确预设为该值。因此不需要笨拙地去设置。 - BalusC
我直觉认为BalusC是正确的。我尝试使用您的建议,但由于我需要提供所创建的页面,因此我没有权限(该文件被存储在本地临时目录中)。因此,也许我没有正确配置Eclipse,应用程序没有正确部署,因此它没有访问Tomcat临时目录,而是访问了机器的目录。@A4L也暗示了这一点,所以我认为我必须仔细检查我的设置... - Iordan Iordanov
java.io.tmpdir 是每个 JVM 全局的,我认为 Servlet 上下文属性会为每个 Web 应用程序提供不同的目录(例如在 Tomcat 下的 $CATALINA_HOME/work)? - Ian Roberts
一般来说,将临时内容生成到磁盘上的文件中,然后让容器提供服务,最后再将其删除,这是灾难的配方:容器将尝试缓存该文件等,会导致混乱。 - Christopher Schultz

0

在用户注销时,确保您已关闭文件并检查File#delete()返回的内容,然后再尝试删除它。

@Test
public void createTempFile() throws IOException {
    File tf = File.createTempFile("hello", ".tmp", new File("."));
    FileOutputStream fos = new FileOutputStream(tf);
    fos.write("Hello, Temp!".getBytes());
    Assert.assertTrue(tf.delete()); // fails because the file could not be deleted
                                    // and delete() returns false
}

对比。

@Test
public void createTempFile() throws IOException {
    File tf = File.createTempFile("hello", ".tmp", new File("."));
    FileOutputStream fos = new FileOutputStream(tf);
    fos.write("Hello, Temp!".getBytes());
    fos.close();
    Assert.assertTrue(tf.delete()); // passes, file deleted
}

使用File#deleteOnExit(),文件将在虚拟机退出时被删除,这会在Tomcat关闭时发生。因此,它对用户注销没有帮助。

编辑

确保每个用户只有一个文件,并且跨多个请求。建议使用像Joop建议的SessionListener,在HttpSessionListener#sessionCreated被调用时创建文件,并使用已知的键将其放入会话中,您可以使用HttpSessionEvent#getSession()获取会话对象。当您注销时调用HttpSession.#invalidate(),Listner方法HttpSessionListener#sessionDestroyed将被调用,然后您可以从会话中获取文件并将其删除。
简单示例(仅使用doGet且没有SessionListener)
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet(urlPatterns = "/ptf.html")
public class PopulateTempFile extends HttpServlet { 
    private static final long serialVersionUID = -144949663400032218L;

    private static class TempFilePopulator {
        private File tf = null;
        public TempFilePopulator(String rootDir) throws IOException {
            tf = File.createTempFile("hello", ".tmp", new File(rootDir));
        }

        public void populate(String line) throws IOException {
            FileWriter fw = new FileWriter(tf, true);
            fw.write(line + "\n");
            fw.close();
        }

        public List<String> getContent() throws IOException {
            List<String> lines = new ArrayList<String>();
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(tf)));
            String line;
            while(null != (line = br.readLine())) {
                lines.add(line);
            }
            br.close();
            return lines;
        }

        public boolean deleteTempFile() { return tf.delete(); }
        public String toString() { return tf.getAbsolutePath(); }
    }


    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {

        HttpSession session = request.getSession();
        TempFilePopulator tfp = (TempFilePopulator) session.getAttribute("tempfilepopulator");

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");

        out.println("<a href=\"" + request.getServletContext().getContextPath()
            + request.getServletPath() + "\">Refresh</a>");
        out.println("&nbsp;|&nbsp;");
        out.println("<a href=\"" + request.getServletContext().getContextPath()
            + request.getServletPath() + "?logout=true\">Logout</a>");

        String logout = request.getParameter("logout");
        if("true".equals(logout)) {
            if(tfp != null) {
                if(tfp.deleteTempFile()) {
                    log("Temp file '" + tfp + "' deleted.");
                } else {
                    log("Unable to delete temp file '" + tfp + "'");
                }
            }
            session.invalidate();
        } else {
            if(tfp == null) {
                tfp = new TempFilePopulator(request.getServletContext().getRealPath("/"));
                log("Temp file '" + tfp + "' created.");
                session.setAttribute("tempfilepopulator", tfp);
            }
            tfp.populate(new Date().toString());
            out.println("<p>Content of temp file</p>");
            List<String> lines = tfp.getContent();
            for(String line : lines) {
                out.write(line);
                out.write("<br/>");
            }
        }
        out.println("</html>");
    }
}

我已经验证了文件已关闭,甚至更改了写文件的方式以反映您的方式,但仍然没有任何进展... :/ - Iordan Iordanov
我目前处于测试阶段,因此我是唯一使用该系统的用户。我同意必须实现会话监听器,正如@Joop所建议的那样。为了初学者尽可能简单地尝试它,我省略了会话监听器。我将编辑我的初始帖子以添加细节。 - Iordan Iordanov
@IMIordanov 我已经编辑了我的答案,并根据你在问题中提供的细节添加了一个简单的示例。 - A4L
太好了,非常感谢!:D 我刚刚完成了自己的SessionListener的实现,但结果并不是我所期望的...请看编辑 :) - Iordan Iordanov

0

您还可以使用deleteOnExit()方法...请查看createTempFile()的Java文档 -

Creates a new empty file in the specified directory, using the given prefix and 
suffix strings to generate its name. If this method returns successfully then 
it is guaranteed that:

The file denoted by the returned abstract pathname did not exist before this 
method was invoked and , 

Neither this method nor any of its variants will return the same abstract 
pathname again in the current invocation of the virtual machine. 

This method provides only part of a temporary-file facility. To arrange 
for a file created by this method to be deleted automatically, use the
deleteOnExit() method.

是的,但正如我在最初的帖子中所说,该文件将在“onExit”时被删除,也就是当Tomcat关闭时。我不想这样,我想在Tomcat运行时手动删除该文件。 - Iordan Iordanov
你在使用BufferedWriter来写文件内容吗?如果是的话,尝试使用FileOutputStream - 谢谢。 - saurav
更多信息请参考此问题 - https://dev59.com/83NA5IYBdhLWcg3wWsiK - saurav
我之前使用的是BufferedWriter,现在换成了FileOutputStream :) - Iordan Iordanov
我肯定会避免在Tomcat中使用deleteOnExit函数。它会导致内存泄漏并死机。File.deleteOnExit memory leak information - mbarlocker

0

每次调用createTempFile都会给出另一个路径,因此必须存储该路径。

请参见SessionListenerExample - 如果涉及会话超时。

也许可以使用JSESSIONID作为临时文件的目录并删除该目录。

顺便说一下,我假设您在file.delete()之后使会话无效,否则getSession()将创建一个新会话。 我会记录file.getPath()


我在创建/删除过程中在控制台上打印消息,这是我得到的内容: Created file: F:\workspace\eclipse\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\JavaTestServlet\tmp\getter423480123629841021.html 然后,在删除它时,我得到以下内容: Deleting file: F:\workspace\eclipse\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\JavaTestServlet\tmp\getter423480123629841021.html --> FAILURE 请注意,第二条消息的最后一部分(--> FAILURE)是我自己添加的,表示在删除后 file.exists()true - Iordan Iordanov
那么 @A4L 看起来是正确的,文件没有被关闭,正在使用中。这也可能是由于病毒扫描器或自动备份引起的,但不太可能。 - Joop Eggen
嗯...我明白了,也许它仍然在会话中打开,尽管我关闭了FileOutputStream。我将尝试实现SessionListener :) - Iordan Iordanov

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