在 servlet 应用程序中保存上载文件的推荐方法

我读 给你,一个人不应该保存在服务器的文件无论如何,因为它是不可移植的,事务性和需要外部参数。但是,考虑到我需要 tomcat (7)的 tmp 解决方案,并且我对我想知道的服务器机器有(相对)控制权:

  • 保存文件的最佳位置是什么?我应该把它保存在 /WEB-INF/uploads(建议反对 给你)或 $CATALINA_BASE下的某个地方(见 给你)或... ?JavaEE 6教程 gets the path from the user(: 卧槽:)。注意: 该文件不应该以任何方式下载。

  • 我是否应该设置一个配置参数作为详细的 给你?我想要一些代码(我宁愿给它一个相对的路径-这样它至少是 Tomcat 可移植的)-Part.write()看起来很有前途-但显然需要一个绝对路径

  • 我有兴趣阐述这种方法相对于数据库/JCR 存储库方法的缺点

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

A solution easily convertible to use a DB or a JCR implementation (like 长耳兔) would be preferable.

150966 次浏览

将它存储在 IDE 的项目文件夹(即服务器的部署文件夹)的 except可访问位置的任何地方,原因在答案 Uploaded image only available after refreshing the page中提到:

  1. IDE 项目文件夹中的更改不会立即反映在服务器的工作文件夹中。IDE 中有一种后台作业,它负责将服务器的工作文件夹与最近的更新同步(在 IDE 术语中称为“发布”)。这是你看到的问题的主要原因。

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

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

对于我或者其他任何人来说,只要你是 永远不要使用 getRealPath()方法,它在本地磁盘文件系统的什么地方被保存真的无关紧要。使用该方法是在 任何情况下报警。

存储位置的路径可以通过多种方式进行定义。你必须通过 你自己完成这一切。也许这就是造成混淆的原因,因为您以某种方式期望服务器自动完成这一切。请注意,@MultipartConfig(location)确实指定了 没有的最终上传目的地,但是案例文件大小的临时存储位置超过了内存存储阈值。

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

  • 硬编码:

      File uploads = new File("/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"));
    
  • 作为 upload.location=/path/to/uploads*.properties文件条目:

      File uploads = new File(properties.getProperty("upload.location"));
    
  • 名称为 upload.location,值为 /path/to/uploadsweb.xml <context-param>:

      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 中获得 partHow to upload file using JSF 2.2 <h:inputFile>? Where is the saved File?中有答案

注意: 没有是否使用 Part#write()来解释相对于 @MultipartConfig(location)中定义的临时存储位置的路径。还要绝对确保在读/写过程中不正确地使用 Reader/Writer而不是 InputStream/OutputStream将字节转换为字符,从而不会损坏二进制文件,如 PDF 文件或图像文件。

参见:

我发布了我最后的做法,基于公认的答案:

@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:/_/

如果你发现一个错误让我知道