将上传文件保存在Servlet应用程序中的推荐方法


121

在这里读到,无论如何该文件都不应该保存在服务器中,因为它不可移植,具有事务性并且需要外部参数。但是,鉴于我需要一个适用于tomcat(7)的tmp解决方案,并且可以(相对)控制我想知道的服务器计算机:

  • 保存文件的最佳位置是什么?我应该将其保存在/WEB-INF/uploads(建议此处使用)还是下面$CATALINA_BASE(请参见此处)或...的某个位置?JavaEE 6教程从用户(:wtf :) 获取路径。注意:该文件不应以任何方式下载。

  • 是否应按此处详细说明设置配置参数?我会喜欢一些代码(我希望给它一个相对路径-因此它至少是Tomcat可移植的)- Part.write()看起来很有希望-但显然需要一个绝对路径

  • 我想对这种方法相对于数据库/ JCR存储库的缺点进行阐述。

不幸的是 ,@ BalusC 的FileServlet专注于下载文件,而他对上传文件的回答却忽略了保存文件的位置。

易于转换为使用DB或JCR实现的解决方案(如jackrabbit)将是可取的。


对于我最终的处理方式,请参见下面的答案
Mr_and_Mrs_D

Answers:


165

将其存储在IDE的项目文件夹(又称为服务器的deploy文件夹)以外的任何可访问位置中,其原因仅在刷新页面后可用的“载图像 ”答案中提到:

  1. IDE的项目文件夹中的更改不会立即反映在服务器的工作文件夹中。IDE中有一种后台作业,用于确保服务器的工作文件夹与上次更新同步(这在IDE中称为“发布”)。这是您遇到问题的主要原因。

  2. 在现实世界中的代码中,有时无法将上传的文件存储在webapp的deploy文件夹中。某些服务器(默认情况下或通过配置)不会将已部署的WAR文件扩展到本地磁盘文件系统中,而是完全扩展到内存中。如果没有基本编辑已部署的WAR文件并重新部署它,就无法在内存中创建新文件。

  3. 即使服务器将已部署的WAR文件扩展到本地磁盘文件系统中,所有新创建的文件也将在重新部署甚至是简单的重新启动时丢失,这仅仅是因为这些新文件不属于原始WAR文件。

这其实并不重要,对我或其他人那里正是本地磁盘文件系统将被保存,只要你曾经使用过getRealPath()的方法。在任何情况下,使用该方法都是令人震惊的。

到存储位置的路径又可以通过多种方式定义。你必须全部由做自己。也许这是造成混乱的原因,因为您不知何故希望服务器自动完成所有工作。请注意,@MultipartConfig(location)没有指定最后上传目的地,但对于案件的文件大小的临时存储位置超过存储器阈值。

因此,可以通过以下两种方式定义到最终存储位置的路径:

  • 硬编码:

      File uploads = new File("/path/to/uploads");
  • 环境变量通过SET UPLOAD_LOCATION=/path/to/uploads

      File uploads = new File(System.getenv("UPLOAD_LOCATION"));
  • 通过-Dupload.location="/path/to/uploads"以下命令在服务器启动期间的VM参数:

      File uploads = new File(System.getProperty("upload.location"));
  • *.properties文件输入为upload.location=/path/to/uploads

      File uploads = new File(properties.getProperty("upload.location"));
  • web.xml <context-param>具有名称upload.location和值/path/to/uploads

      File uploads = new File(getServletContext().getInitParameter("upload.location"));
  • 如果有,请使用服务器提供的位置,例如在JBoss AS / WildFly中

      File uploads = new File(System.getProperty("jboss.server.data.dir"), "uploads");

无论哪种方式,您都可以轻松地参考和保存文件,如下所示:

File file = new File(uploads, "somefilename.ext");

try (InputStream input = part.getInputStream()) {
    Files.copy(input, file.toPath());
}

或者,当您要自动生成唯一的文件名以防止用户覆盖名称恰好相同的现有文件时:

File file = File.createTempFile("somefilename-", ".ext", uploads);

try (InputStream input = part.getInputStream()) {
    Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}

如何使用JSP / Servlet将文件上传到服务器,part回答了如何在JSP / Servlet中获取文件?以及如何使用JSF 2.2 <h:inputFile>上传文件中part回答了如何在JSF中获取文件?保存的文件在哪里?

注意:千万不能使用Part#write(),因为它相对于定义的临时存储位置的路径解释@MultipartConfig(location)

也可以看看:


@MultipartConfig(location)指定临时,当文件大小超过了存储器的门槛服务器应该使用STORGE位置,不是你倒是最终喜欢它的永久存储位置来进行存储。该值默认为java.io.tmpdir系统属性标识的路径。另见本相关答案失败的尝试JSF:stackoverflow.com/questions/18478154/...
BalusC

1
谢谢-希望我听起来不会白痴,但是Part.write>>中的引号。这允许特定的实现在可能的情况下使用例如文件重命名,而不是复制所有基础数​​据,从而在与某些功能结合使用时获得显着的性能优势未知的“剪切”(vs复制)方法,例如说一些apache lib可以节省我自己写字节的麻烦-并在那里重新创建文件(请参见此处
Mr_and_Mrs_D 2013年

是的,如果您已经使用Servlet 3.0,则可以使用Part#write()。我用它更新了答案。
BalusC 2013年

非常感谢您保持帖子的更新-Tomcat是否具有这样的属性"jboss.server.data.dir"
Mr_and_Mrs_D 2013年

1
不,它没有。
BalusC

7

我根据接受的答案发布了最终的解决方法:

@SuppressWarnings("serial")
@WebServlet("/")
@MultipartConfig
public final class DataCollectionServlet extends Controller {

    private static final String UPLOAD_LOCATION_PROPERTY_KEY="upload.location";
    private String uploadsDirName;

    @Override
    public void init() throws ServletException {
        super.init();
        uploadsDirName = property(UPLOAD_LOCATION_PROPERTY_KEY);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // ...
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Collection<Part> parts = req.getParts();
        for (Part part : parts) {
            File save = new File(uploadsDirName, getFilename(part) + "_"
                + System.currentTimeMillis());
            final String absolutePath = save.getAbsolutePath();
            log.debug(absolutePath);
            part.write(absolutePath);
            sc.getRequestDispatcher(DATA_COLLECTION_JSP).forward(req, resp);
        }
    }

    // helpers
    private static String getFilename(Part part) {
        // courtesy of BalusC : http://stackoverflow.com/a/2424824/281545
        for (String cd : part.getHeader("content-disposition").split(";")) {
            if (cd.trim().startsWith("filename")) {
                String filename = cd.substring(cd.indexOf('=') + 1).trim()
                        .replace("\"", "");
                return filename.substring(filename.lastIndexOf('/') + 1)
                        .substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
            }
        }
        return null;
    }
}

在哪里:

@SuppressWarnings("serial")
class Controller extends HttpServlet {

    static final String DATA_COLLECTION_JSP="/WEB-INF/jsp/data_collection.jsp";
    static ServletContext sc;
    Logger log;
    // private
    // "/WEB-INF/app.properties" also works...
    private static final String PROPERTIES_PATH = "WEB-INF/app.properties";
    private Properties properties;

    @Override
    public void init() throws ServletException {
        super.init();
        // synchronize !
        if (sc == null) sc = getServletContext();
        log = LoggerFactory.getLogger(this.getClass());
        try {
            loadProperties();
        } catch (IOException e) {
            throw new RuntimeException("Can't load properties file", e);
        }
    }

    private void loadProperties() throws IOException {
        try(InputStream is= sc.getResourceAsStream(PROPERTIES_PATH)) {
                if (is == null)
                    throw new RuntimeException("Can't locate properties file");
                properties = new Properties();
                properties.load(is);
        }
    }

    String property(final String key) {
        return properties.getProperty(key);
    }
}

和/WEB-INF/app.properties:

upload.location=C:/_/

HTH,如果您发现错误,请告诉我


1
如果我想要一个在两种(win / ux)情况下都适用的SO独立解决方案怎么办?我是否必须设置不同的upload.location路径,还是有其他提示?
pikimota
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.