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

View File

@ -17,15 +17,16 @@
package org.springframework.core.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import org.springframework.util.Assert;
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}.
* 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
* @since 28.12.2003
* @see PathResource
* @see java.io.File
* @see java.nio.file.Files
*/
public class FileSystemResource extends AbstractResource implements WritableResource {
@ -113,7 +120,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/
@Override
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
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
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
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 Juergen Hoeller
* @since 4.0
* @see FileSystemResource
* @see java.nio.file.Path
* @see java.nio.file.Files
*/
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.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root:
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @param path a Path handle
*/
public PathResource(Path path) {
@ -65,8 +66,7 @@ public class PathResource extends AbstractResource implements WritableResource {
* Create a new PathResource from a Path handle.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root:
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @param path a path
* @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.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root:
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @see java.nio.file.Paths#get(URI)
* @param uri a path URI
*/
@ -193,7 +192,7 @@ public class PathResource extends AbstractResource implements WritableResource {
catch (UnsupportedOperationException ex) {
// Only paths on the default file system can be converted to a File:
// 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 {
// 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...
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.
* @see java.nio.file.Path#resolve(String)
*/

View File

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

View File

@ -18,6 +18,11 @@ package org.springframework.util;
import java.io.File;
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;
@ -27,29 +32,60 @@ import org.springframework.lang.Nullable;
* @author Rob Harrop
* @author Juergen Hoeller
* @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 {
/**
* Delete the supplied {@link File} - for directories,
* 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
* @return {@code true} if the {@code File} was deleted,
* @return {@code true} if the {@code File} was successfully deleted,
* otherwise {@code false}
*/
public static boolean deleteRecursively(@Nullable File root) {
if (root != null && root.exists()) {
if (root.isDirectory()) {
File[] children = root.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursively(child);
}
}
if (root != null) {
try {
return deleteRecursively(root.toPath());
}
return root.delete();
catch (IOException ex) {
return false;
}
}
return false;
}
/**
* 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;
}
@ -61,43 +97,44 @@ public abstract class FileSystemUtils {
* @param dest the destination directory
* @throws IOException in the case of I/O errors
*/
public static void copyRecursively(@Nullable File src, File dest) throws IOException {
Assert.isTrue(src != null && (src.isDirectory() || src.isFile()),
"Source File must denote a directory or file");
public static void copyRecursively(File src, File dest) throws IOException {
Assert.notNull(src, "Source 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.
* @param src the source directory
* @param dest the destination directory
* @throws IOException in the case of I/O errors
* @since 5.0
*/
private static void doCopyRecursively(File src, File dest) throws IOException {
if (src.isDirectory()) {
dest.mkdir();
File[] entries = src.listFiles();
if (entries == null) {
throw new IOException("Could not list files in directory: " + src);
}
for (File entry : entries) {
doCopyRecursively(entry, new File(dest, entry.getName()));
}
public static void copyRecursively(Path src, Path dest) throws IOException {
Assert.notNull(src, "Source Path must not be null");
Assert.notNull(dest, "Destination Path must not be null");
BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
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;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, dest.resolve(src.relativize(file)));
return FileVisitResult.CONTINUE;
}
});
}
else if (src.isFile()) {
try {
dest.createNewFile();
}
catch (IOException ex) {
throw new IOException("Failed to create file: " + dest, ex);
}
FileCopyUtils.copy(src, dest);
else if (srcAttr.isRegularFile()) {
Files.copy(src, dest);
}
else {
// Special File handle: neither a file not a directory.
// Simply skip it when contained in nested directory...
throw new IllegalArgumentException("Source File must denote a directory or file");
}
}

View File

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

View File

@ -16,9 +16,6 @@
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.URLClassLoader;
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.

View File

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

View File

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

View File

@ -17,12 +17,12 @@
package org.springframework.web.multipart.support;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
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
// 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.
FileCopyUtils.copy(this.part.getInputStream(), new FileOutputStream(dest));
FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath()));
}
}
}