【问题标题】:Java Servlet: delete temporary fileJava Servlet:删除临时文件
【发布时间】:2013-07-26 08:59:39
【问题描述】:

我正在开发一个 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 提供的示例。我是不是做错了什么……?

【问题讨论】:

  • 你在哪里看到文件还在?您是使用文件 esxplorer 还是在 IDE 中的项目中看到它(例如,在 Eclipse 中,您必须刷新项目,然后该文件将从视图中消失)。您确定该文件不是之前运行的僵尸文件,而之前的删除可能还没有奏效吗?
  • 是的,我忘了提这个。正如我在另一条评论中所说,我在各个地方都有文件输出的全名。所以,在创建文件时,我得到: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 但是可以从文件资源管理器中看到该文件...
  • 我尝试在删除之前添加检查文件是否存在。所以,我知道该文件存在,然后它被成功删除,然后它不再存在。这没关系。只是该文件仍然驻留在文件系统上...
  • 这很奇怪:S ...据我所知,您在 Eclipse 中将您的项目设置为Dynamic web project。在这种情况下,eclipse 管理您的 web-content 目录,该目录位于您工作区的 .metadata 目录中的某个位置。也许日食在那里做了一些奇怪的事情。您应该尝试手动将您的项目部署到独立的 tomcat(没有 eclipse)中,并查看删除后文件是否仍然存在。
  • 正如我在@BalusC 的帖子中评论的那样,我也许可以消除临时文件。在最后一次尝试中,我什至无法创建临时文件,所以我认为没有它们会更好。非常感谢您的建议,非常宝贵:)

标签: java tomcat servlets


【解决方案1】:

请停止在运行时将任意文件写入部署文件夹。只需写入一个真正的临时文件夹即可。完全摆脱以下行:

path = request.getSession().getServletContext().getRealPath("/");

然后使用

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

您的具体问题可能是因为部署文件夹中的文件通常被 servletcontainer 锁定。您不能删除其中的文件。

对未来的提示:每当您认为getRealPath() 可以解决问题时,您应该立即停止编写代码并三思而后行是否是解决具体问题的正确工具。在我开发基于 Servlet 的 Web 应用程序的十年中,该方法在现实世界中没有一个明智的用例。另见What does servletcontext.getRealPath("/") mean and when should I use it


我知道 file.deleteOnExit(),但是...我什么时候退出 servlet?

你没有。容器可以。这里的“退出”基本上是指整个JVM的关闭。 the javadoc(强调我的)中甚至直接提到了这一点。

请求在虚拟机终止时删除此抽象路径名表示的文件或目录

【讨论】:

  • 感谢您的洞察力 :) 有经验丰富的人给您提示总是很高兴的。我想我将能够彻底根除临时文件的使用,也许它会更好地工作。我会看看你包含的文章以供将来参考:)
  • 在 webapp 自己的临时目录 ((File)servletContext.getAttribute(ServletContext.TEMPDIR)) 中创建临时文件可能是个好主意
  • @Ian:File#createTempFile() 使用 java.io.tmpdir,它已经被容器正确地预设为该值。所以没有必要笨拙。
  • 我凭直觉相信 BalusC 是正确的。我尝试使用您的建议,但由于我需要为创建的页面提供服务,因此我没有权限(文件存储在 local 临时目录中)。所以,也许我没有正确配置 Eclipse 并且应用程序没有正确部署,因此它没有访问 Tomcat 临时目录,而是机器的。 @A4L 也暗示了这一点,所以我认为我必须更好地查看我的设置......
  • java.io.tmpdir 是全局的 per-JVM,我认为 servlet 上下文属性为每个 webapp 提供了不同的目录(例如,在 Tomcat 上的 $CATALINA_HOME/work 下)?
【解决方案2】:

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
  • 然后@A4L 似乎是正确的,并且文件没有关闭,正在使用中。病毒扫描程序或自动备份也可能发生这种情况,但不太可能。
  • 嗯...我明白了,也许它在会话中仍然打开,虽然我关闭了FileOutputStream。我会尝试实现SessionListener :)
【解决方案3】:

您也可以使用 deleteOnExit() 方法...请查看 createTempFile() 的 java doc -

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运行时手动删除文件。
  • 您是否使用 BufferedWriter 将内容写入文件?如果是,请尝试使用 FileOutputStream - 谢谢
  • 我用的是BufferedWriter,后来改成了FileOutputStream :)
  • 我肯定会避免 Tomcat 中的 deleteOnExit 函数。它将导致内存泄漏并死亡。 File.deleteOnExit memory leak information
【解决方案4】:

确保在用户注销时尝试将其删除之前已关闭该文件,并检查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() 文件将在 VM 退出时被删除,这会在您的 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>");
    }
}

【讨论】:

  • 我已经验证文件已关闭,甚至更改了我编写文件的方式以反映您的情况,但仍然没有...:/
  • 我目前处于测试阶段,因此我是唯一使用该系统的用户。我同意我必须实现一个会话监听器,正如@Joop 建议的那样。我想为初学者尽可能简单地尝试它,因此缺少会话侦听器。我将编辑我的初始帖子以添加详细信息。
  • @IMIordanov 我已经编辑了我的答案,并根据您在问题中添加的详细信息添加了一个简单的示例。
  • 很好,非常感谢! :D 我刚刚完成了我自己的 SessionListener 的实现,但结果不是我所期望的......请参阅编辑 :)
猜你喜欢
  • 1970-01-01
  • 2010-11-04
  • 1970-01-01
  • 2013-05-17
  • 1970-01-01
  • 1970-01-01
  • 2018-11-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多