Consistent use of NIO.2 for file read/write interactions

Issue: SPR-15748
This commit is contained in:
Juergen Hoeller 2017-07-18 00:54:41 +02:00
parent d56fedc226
commit 12114a9d4c
10 changed files with 118 additions and 125 deletions

View File

@ -17,7 +17,6 @@
package org.springframework.core.io; package org.springframework.core.io;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -25,7 +24,9 @@ import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.file.StandardOpenOption;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
@ -127,7 +128,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
@Override @Override
public ReadableByteChannel readableChannel() throws IOException { public ReadableByteChannel readableChannel() throws IOException {
if (isFile()) { if (isFile()) {
return new FileInputStream(getFile()).getChannel(); return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
} }
else { else {
return super.readableChannel(); return super.readableChannel();

View File

@ -17,15 +17,16 @@
package org.springframework.core.io; package org.springframework.core.io;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel; import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -35,9 +36,15 @@ import org.springframework.util.StringUtils;
* Supports resolution as a {@code File} and also as a {@code URL}. * Supports resolution as a {@code File} and also as a {@code URL}.
* Implements the extended {@link WritableResource} interface. * Implements the extended {@link WritableResource} interface.
* *
* <p>Note: As of Spring Framework 5.0, this {@link Resource} implementation
* uses NIO.2 API for read/write interactions. Nevertheless, in contrast to
* {@link PathResource}, it primarily manages a {@code java.io.File} handle.
*
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 28.12.2003 * @since 28.12.2003
* @see PathResource
* @see java.io.File * @see java.io.File
* @see java.nio.file.Files
*/ */
public class FileSystemResource extends AbstractResource implements WritableResource { public class FileSystemResource extends AbstractResource implements WritableResource {
@ -113,7 +120,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/ */
@Override @Override
public InputStream getInputStream() throws IOException { public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file); return Files.newInputStream(this.file.toPath());
} }
/** /**
@ -133,7 +140,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/ */
@Override @Override
public OutputStream getOutputStream() throws IOException { public OutputStream getOutputStream() throws IOException {
return new FileOutputStream(this.file); return Files.newOutputStream(this.file.toPath());
} }
/** /**
@ -176,7 +183,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/ */
@Override @Override
public ReadableByteChannel readableChannel() throws IOException { public ReadableByteChannel readableChannel() throws IOException {
return new FileInputStream(this.file).getChannel(); return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
} }
/** /**
@ -185,7 +192,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/ */
@Override @Override
public WritableByteChannel writableChannel() throws IOException { public WritableByteChannel writableChannel() throws IOException {
return new FileOutputStream(this.file).getChannel(); return FileChannel.open(getFile().toPath(), StandardOpenOption.WRITE);
} }
/** /**

View File

@ -41,7 +41,9 @@ import org.springframework.util.Assert;
* @author Philippe Marschall * @author Philippe Marschall
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 4.0 * @since 4.0
* @see FileSystemResource
* @see java.nio.file.Path * @see java.nio.file.Path
* @see java.nio.file.Files
*/ */
public class PathResource extends AbstractResource implements WritableResource { public class PathResource extends AbstractResource implements WritableResource {
@ -52,8 +54,7 @@ public class PathResource extends AbstractResource implements WritableResource {
* Create a new PathResource from a Path handle. * Create a new PathResource from a Path handle.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i> * via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root: * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @param path a Path handle * @param path a Path handle
*/ */
public PathResource(Path path) { public PathResource(Path path) {
@ -65,8 +66,7 @@ public class PathResource extends AbstractResource implements WritableResource {
* Create a new PathResource from a Path handle. * Create a new PathResource from a Path handle.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i> * via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root: * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @param path a path * @param path a path
* @see java.nio.file.Paths#get(String, String...) * @see java.nio.file.Paths#get(String, String...)
*/ */
@ -79,8 +79,7 @@ public class PathResource extends AbstractResource implements WritableResource {
* Create a new PathResource from a Path handle. * Create a new PathResource from a Path handle.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i> * via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root: * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @see java.nio.file.Paths#get(URI) * @see java.nio.file.Paths#get(URI)
* @param uri a path URI * @param uri a path URI
*/ */
@ -193,7 +192,7 @@ public class PathResource extends AbstractResource implements WritableResource {
catch (UnsupportedOperationException ex) { catch (UnsupportedOperationException ex) {
// Only paths on the default file system can be converted to a File: // Only paths on the default file system can be converted to a File:
// Do exception translation for cases where conversion is not possible. // Do exception translation for cases where conversion is not possible.
throw new FileNotFoundException(this.path + " cannot be resolved to " + "absolute file path"); throw new FileNotFoundException(this.path + " cannot be resolved to absolute file path");
} }
} }
@ -231,11 +230,11 @@ public class PathResource extends AbstractResource implements WritableResource {
public long lastModified() throws IOException { public long lastModified() throws IOException {
// We can not use the superclass method since it uses conversion to a File and // We can not use the superclass method since it uses conversion to a File and
// only a Path on the default file system can be converted to a File... // only a Path on the default file system can be converted to a File...
return Files.getLastModifiedTime(path).toMillis(); return Files.getLastModifiedTime(this.path).toMillis();
} }
/** /**
* This implementation creates a FileResource, applying the given path * This implementation creates a PathResource, applying the given path
* relative to the path of the underlying file of this resource descriptor. * relative to the path of the underlying file of this resource descriptor.
* @see java.nio.file.Path#resolve(String) * @see java.nio.file.Path#resolve(String)
*/ */

View File

@ -16,19 +16,16 @@
package org.springframework.util; package org.springframework.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader; import java.io.Reader;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.nio.file.Files;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -42,6 +39,7 @@ import org.springframework.lang.Nullable;
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 06.10.2003 * @since 06.10.2003
* @see StreamUtils * @see StreamUtils
* @see FileSystemUtils
*/ */
public abstract class FileCopyUtils { public abstract class FileCopyUtils {
@ -62,9 +60,7 @@ public abstract class FileCopyUtils {
public static int copy(File in, File out) throws IOException { public static int copy(File in, File out) throws IOException {
Assert.notNull(in, "No input File specified"); Assert.notNull(in, "No input File specified");
Assert.notNull(out, "No output File specified"); Assert.notNull(out, "No output File specified");
return copy(Files.newInputStream(in.toPath()), Files.newOutputStream(out.toPath()));
return copy(new BufferedInputStream(new FileInputStream(in)),
new BufferedOutputStream(new FileOutputStream(out)));
} }
/** /**
@ -76,10 +72,7 @@ public abstract class FileCopyUtils {
public static void copy(byte[] in, File out) throws IOException { public static void copy(byte[] in, File out) throws IOException {
Assert.notNull(in, "No input byte array specified"); Assert.notNull(in, "No input byte array specified");
Assert.notNull(out, "No output File specified"); Assert.notNull(out, "No output File specified");
copy(new ByteArrayInputStream(in), Files.newOutputStream(out.toPath()));
ByteArrayInputStream inStream = new ByteArrayInputStream(in);
OutputStream outStream = new BufferedOutputStream(new FileOutputStream(out));
copy(inStream, outStream);
} }
/** /**
@ -90,8 +83,7 @@ public abstract class FileCopyUtils {
*/ */
public static byte[] copyToByteArray(File in) throws IOException { public static byte[] copyToByteArray(File in) throws IOException {
Assert.notNull(in, "No input File specified"); Assert.notNull(in, "No input File specified");
return copyToByteArray(Files.newInputStream(in.toPath()));
return copyToByteArray(new BufferedInputStream(new FileInputStream(in)));
} }

View File

@ -18,6 +18,11 @@ package org.springframework.util;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -27,29 +32,60 @@ import org.springframework.lang.Nullable;
* @author Rob Harrop * @author Rob Harrop
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 2.5.3 * @since 2.5.3
* @deprecated as of Spring Framework 5.0, in favor of native NIO API usage * @see java.io.File
* @see java.nio.file.Path
* @see java.nio.file.Files
*/ */
@Deprecated
public abstract class FileSystemUtils { public abstract class FileSystemUtils {
/** /**
* Delete the supplied {@link File} - for directories, * Delete the supplied {@link File} - for directories,
* recursively delete any nested directories or files as well. * recursively delete any nested directories or files as well.
* <p>Note: Like {@link File#delete()}, this method does not throw any
* exception but rather silently returns {@code false} in case of I/O
* errors. Consider using {@link #deleteRecursively(Path)} for NIO-style
* handling of I/O errors, clearly differentiating between non-existence
* and failure to delete an existing file.
* @param root the root {@code File} to delete * @param root the root {@code File} to delete
* @return {@code true} if the {@code File} was deleted, * @return {@code true} if the {@code File} was successfully deleted,
* otherwise {@code false} * otherwise {@code false}
*/ */
public static boolean deleteRecursively(@Nullable File root) { public static boolean deleteRecursively(@Nullable File root) {
if (root != null && root.exists()) { if (root != null) {
if (root.isDirectory()) { try {
File[] children = root.listFiles(); return deleteRecursively(root.toPath());
if (children != null) { }
for (File child : children) { catch (IOException ex) {
deleteRecursively(child); return false;
} }
} }
return false;
} }
return root.delete();
/**
* Delete the supplied {@link File} - for directories,
* recursively delete any nested directories or files as well.
* @param root the root {@code File} to delete
* @return {@code true} if the {@code File} existed and was deleted,
* or {@code false} it it did not exist
* @throws IOException in the case of I/O errors
* @since 5.0
*/
public static boolean deleteRecursively(@Nullable Path root) throws IOException {
if (root != null) {
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
return Files.deleteIfExists(root);
} }
return false; return false;
} }
@ -61,43 +97,44 @@ public abstract class FileSystemUtils {
* @param dest the destination directory * @param dest the destination directory
* @throws IOException in the case of I/O errors * @throws IOException in the case of I/O errors
*/ */
public static void copyRecursively(@Nullable File src, File dest) throws IOException { public static void copyRecursively(File src, File dest) throws IOException {
Assert.isTrue(src != null && (src.isDirectory() || src.isFile()), Assert.notNull(src, "Source File must not be null");
"Source File must denote a directory or file");
Assert.notNull(dest, "Destination File must not be null"); Assert.notNull(dest, "Destination File must not be null");
doCopyRecursively(src, dest); copyRecursively(src.toPath(), dest.toPath());
} }
/** /**
* Actually copy the contents of the {@code src} file/directory * Recursively copy the contents of the {@code src} file/directory
* to the {@code dest} file/directory. * to the {@code dest} file/directory.
* @param src the source directory * @param src the source directory
* @param dest the destination directory * @param dest the destination directory
* @throws IOException in the case of I/O errors * @throws IOException in the case of I/O errors
* @since 5.0
*/ */
private static void doCopyRecursively(File src, File dest) throws IOException { public static void copyRecursively(Path src, Path dest) throws IOException {
if (src.isDirectory()) { Assert.notNull(src, "Source Path must not be null");
dest.mkdir(); Assert.notNull(dest, "Destination Path must not be null");
File[] entries = src.listFiles(); BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
if (entries == null) {
throw new IOException("Could not list files in directory: " + src); if (srcAttr.isDirectory()) {
Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Files.createDirectories(dest.resolve(src.relativize(dir)));
return FileVisitResult.CONTINUE;
} }
for (File entry : entries) { @Override
doCopyRecursively(entry, new File(dest, entry.getName())); public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, dest.resolve(src.relativize(file)));
return FileVisitResult.CONTINUE;
} }
});
} }
else if (src.isFile()) { else if (srcAttr.isRegularFile()) {
try { Files.copy(src, dest);
dest.createNewFile();
}
catch (IOException ex) {
throw new IOException("Failed to create file: " + dest, ex);
}
FileCopyUtils.copy(src, dest);
} }
else { else {
// Special File handle: neither a file not a directory. throw new IllegalArgumentException("Source File must denote a directory or file");
// Simply skip it when contained in nested directory...
} }
} }

View File

@ -26,7 +26,6 @@ import static org.junit.Assert.*;
/** /**
* @author Rob Harrop * @author Rob Harrop
*/ */
@Deprecated
public class FileSystemUtilsTests { public class FileSystemUtilsTests {
@Test @Test

View File

@ -16,9 +16,6 @@
package org.springframework.expression.spel.standard; package org.springframework.expression.spel.standard;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Map; import java.util.Map;
@ -244,40 +241,6 @@ public class SpelCompiler implements Opcodes {
} }
} }
/**
* For debugging purposes, dump the specified byte code into a file on the disk.
* Not yet hooked in, needs conditionally calling based on a sys prop.
* @param expressionText the text of the expression compiled
* @param name the name of the class being used for the compiled expression
* @param bytecode the bytecode for the generated class
*/
@SuppressWarnings("unused")
private static void dump(String expressionText, String name, byte[] bytecode) {
String nameToUse = name.replace('.', '/');
String dir = (nameToUse.indexOf('/') != -1 ? nameToUse.substring(0, nameToUse.lastIndexOf('/')) : "");
String dumpLocation = null;
try {
File tempFile = File.createTempFile("tmp", null);
dumpLocation = tempFile + File.separator + nameToUse + ".class";
tempFile.delete();
File f = new File(tempFile, dir);
f.mkdirs();
// System.out.println("Expression '" + expressionText + "' compiled code dumped to " + dumpLocation);
if (logger.isDebugEnabled()) {
logger.debug("Expression '" + expressionText + "' compiled code dumped to " + dumpLocation);
}
f = new File(dumpLocation);
FileOutputStream fos = new FileOutputStream(f);
fos.write(bytecode);
fos.flush();
fos.close();
}
catch (IOException ex) {
throw new IllegalStateException(
"Unexpected problem dumping class '" + nameToUse + "' into " + dumpLocation, ex);
}
}
/** /**
* A ChildClassLoader will load the generated compiled expression classes. * A ChildClassLoader will load the generated compiled expression classes.

View File

@ -17,7 +17,6 @@
package org.springframework.http.codec.multipart; package org.springframework.http.codec.multipart;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.channels.Channels; import java.nio.channels.Channels;
@ -25,6 +24,7 @@ import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -187,9 +187,9 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader<Part>
} }
private Part createPart(StreamStorage storage, HttpHeaders httpHeaders) { private Part createPart(StreamStorage storage, HttpHeaders httpHeaders) {
String fileName = MultipartUtils.getFileName(httpHeaders); String filename = MultipartUtils.getFileName(httpHeaders);
if (fileName != null) { if (filename != null) {
return new SynchronossFilePart(httpHeaders, storage, fileName, this.bufferFactory); return new SynchronossFilePart(httpHeaders, storage, this.bufferFactory, filename);
} }
else if (MultipartUtils.isFormField(httpHeaders, this.context)) { else if (MultipartUtils.isFormField(httpHeaders, this.context)) {
String value = MultipartUtils.readFormParameterValue(storage, httpHeaders); String value = MultipartUtils.readFormParameterValue(storage, httpHeaders);
@ -200,13 +200,6 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader<Part>
} }
} }
private Part createPart(HttpHeaders httpHeaders, StreamStorage storage) {
String fileName = MultipartUtils.getFileName(httpHeaders);
return fileName != null ?
new SynchronossFilePart(httpHeaders, storage, fileName, this.bufferFactory) :
new DefaultSynchronossPart(httpHeaders, storage, this.bufferFactory);
}
@Override @Override
public void onError(String message, Throwable cause) { public void onError(String message, Throwable cause) {
if (this.terminated.getAndIncrement() == 0) { if (this.terminated.getAndIncrement() == 0) {
@ -284,15 +277,18 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader<Part>
private static class SynchronossFilePart extends DefaultSynchronossPart implements FilePart { private static class SynchronossFilePart extends DefaultSynchronossPart implements FilePart {
public SynchronossFilePart(HttpHeaders headers, StreamStorage storage, private final String filename;
String fileName, DataBufferFactory factory) {
public SynchronossFilePart(
HttpHeaders headers, StreamStorage storage, DataBufferFactory factory, String filename) {
super(headers, storage, factory); super(headers, storage, factory);
this.filename = filename;
} }
@Override @Override
public String filename() { public String filename() {
return MultipartUtils.getFileName(headers()); return this.filename;
} }
@Override @Override
@ -301,8 +297,7 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader<Part>
FileChannel output = null; FileChannel output = null;
try { try {
input = Channels.newChannel(getStorage().getInputStream()); input = Channels.newChannel(getStorage().getInputStream());
output = new FileOutputStream(destination).getChannel(); output = FileChannel.open(destination.toPath(), StandardOpenOption.WRITE);
long size = (input instanceof FileChannel ? ((FileChannel) input).size() : Long.MAX_VALUE); long size = (input instanceof FileChannel ? ((FileChannel) input).size() : Long.MAX_VALUE);
long totalWritten = 0; long totalWritten = 0;
while (totalWritten < size) { while (totalWritten < size) {

View File

@ -17,10 +17,10 @@
package org.springframework.http.server.reactive; package org.springframework.http.server.reactive;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -85,7 +85,7 @@ public class UndertowServerHttpResponse extends AbstractListenerServerHttpRespon
return doCommit(() -> { return doCommit(() -> {
FileChannel source = null; FileChannel source = null;
try { try {
source = new FileInputStream(file).getChannel(); source = FileChannel.open(file.toPath(), StandardOpenOption.READ);
StreamSinkChannel destination = getUndertowExchange().getResponseChannel(); StreamSinkChannel destination = getUndertowExchange().getResponseChannel();
Channels.transferBlocking(destination, source, position, count); Channels.transferBlocking(destination, source, position, count);
return Mono.empty(); return Mono.empty();

View File

@ -17,12 +17,12 @@
package org.springframework.web.multipart.support; package org.springframework.web.multipart.support;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -317,7 +317,7 @@ public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpSe
// At least we offloaded the file from memory storage; it'll get deleted // At least we offloaded the file from memory storage; it'll get deleted
// from the temp dir eventually in any case. And for our user's purposes, // from the temp dir eventually in any case. And for our user's purposes,
// we can manually copy it to the requested location as a fallback. // we can manually copy it to the requested location as a fallback.
FileCopyUtils.copy(this.part.getInputStream(), new FileOutputStream(dest)); FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath()));
} }
} }
} }