Я использовал некоторые проприетарные фреймворки и библиотеки, которые используют объекты File и только объекты File, повсюду в интерфейсах.
Нет ничего плохого в том, чтобы иметь методы, принимающие объекты File, но не стоит принимать только это. Например, анализатор XML DOM / SAX в JDK принимает объекты File, но включает и другие источники, такие как InputStream , URL и т. Д. Класс Properties даже не принимает объекты File. На самом деле, класс File не очень популярен в интерфейсах хороших библиотек Java, включая JRE.
Причина проста: InputStream гораздо более абстрактный, чем File.
Когда вы указываете, например, [1] , такой интерфейс:
public void sendMail(String email , String message, File attachment);
Вы приковали пользователя цепью, чтобы указать путь к существующему файлу на локальном диске.
Теперь представьте, что вашему приложению необходимо отправить электронное письмо о критической ошибке, происходящей на производстве, чтобы уведомить системных администраторов, и вы хотите включить трассировку стека Исключения, чтобы помочь отследить проблему. Единственный доступный вариант — распечатать трассировку стека во временный файл, вызвать метод, а затем удалить файл.
Это потому, что вложение — это «контент», а не файл. Также конфигурация приложения — это «контент», а не файл. Файл XML и значки ваших приложений — это не просто файлы, это данные и ресурсы. И данные, ресурсы и содержимое, они могут поступать не только из Файлов, но и из удаленных Систем (URL), из баз данных, из JAR-файла в classpath или из других систем, которые вы можете даже не представить.
В приведенном выше примере интерфейс будет намного лучше, если он определен так:
public void sendMail(String email, String message, InputStream attachment);
Теперь … если вы хотите прикрепить трассировку стека, вы можете сделать что-то вроде:
try {
...
} catch (Exception ex) {
sendMail(
"foo@bar.com",
"something bad happened!",
new ThrowableInputStream(ex));
}
Насколько сложно преобразовать исключение в InputStream? Это оно:
public class ThrowableInputStream extends InputStream {
private byte[] bytes;
private int i = 0;
public ThrowableInputStream(Throwable throwable) {
this.bytes = toByteArray(throwable);
}
private byte[] toByteArray(Throwable throwable) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
PrintStream printer = new PrintStream(output);
try {
throwable.printStackTrace(printer);
printer.flush();
return output.toByteArray();
} finally {
printer.close();
}
}
@Override
public int read() throws IOException {
if (i < bytes.length)
return bytes[i++];
else
return -1;
}
}
Выше или проще распечатать исключение во временный файл? И вышеупомянутое менее или более эффективно? Если вы предпочитаете временный файл … по крайней мере, не забудьте удалить файл. И не забудьте проверить, доступна ли файловая система для записи … и убедиться, что на диске осталось достаточно места … и т. Д. И т. Д. …
Вы хотите прикрепить некоторый XML-контент, который вы получили в формате String? Опять же, вместо печати String во временный файл и т. Д. И т. Д. … вы можете написать класс, который преобразует Strings в InputStream:
public class StringInputStream extends InputStream {
private String string;
private int i;
public StringInputStream(String string) {
this.string = string;
}
@Override
public int read() throws IOException {
if (i < string.length())
return string.charAt(i++);
else
return -1;
}
}
Всего 16 строк кода.
Невероятная сила URL
Также конфигурация вашего приложения является частью интерфейса с пользователями … и здесь файлы имеют свои недостатки.
Но давайте пойдем дальше со сложным вариантом использования: вам нужно проанализировать некоторые XML-данные, развернутые внутри zip-файла на удаленном веб-сервере.
У вас есть два варианта:
- Вы можете записать URL файла на веб-сервере в вашу конфигурацию. Ваша программа получит URL из конфигурации, получит доступ к веб-серверу с помощью http-клиента, прочитает заархивированный файл, сохранит его где-нибудь, распакует его, затем проанализирует xml и будет управлять данными. И, наконец, навести порядок.
- Или вы можете указать в своей конфигурации URL-адрес, например «jar: http: //www.partnerwebsite.com/zippedfile.zip! Path / to / data.xml», и передать этот URL-код в анализатор XML. Готово.
Теперь … если вы выбрали второй вариант и требования вашего приложения изменились, и вам нужен доступ к одному и тому же XML-файлу, в сжатом виде или нет, с веб-сервера, с сервера ftp, из локальной файловой системы и т. Д., Вам просто нужно изменить URL в конфигурации. И, если вам нужно получить доступ к ресурсу в протоколе, который изначально не поддерживается URL, вы можете реализовать URLStreamHandler .
Да, URL очень мощный и гибкий в Java. И они возвращают вам InputStream . Не файл! Это не жертва.
Как «починить» библиотеку с такими недостатками?
Ладно … ты несчастный парень, как и я, который должен использовать плохие библиотеки более или менее часто. Как вы исправляете интерфейс как первый метод sendMail? (тот, что с файлом)
Нередко такие плохо спроектированные библиотеки ДЕЙСТВИТЕЛЬНО плохие. Поэтому вы можете написать собственный (объектно-ориентированный) интерфейс поверх него: оболочку или что-то в этом роде. Ваш интерфейс будет использовать InputStream, конечно. Поэтому вам нужно что-то, что преобразует ваш InputStream в файл, который может быть переварен плохой библиотекой.
Вот пример:
public class FileDump {
private static final Random random = new Random();
public static File dump(InputStream input, String fileName) {
File path = createTempDir();
return dump(input, path, fileName);
}
private static File createTempDir() {
String tmpDirName =
System.getProperty("java.io.tmpdir");
File tmpDir = new File(tmpDirName);
File newTempDir = new File(tmpDir,
String.valueOf(random.nextInt()));
newTempDir.mkdirs();
return newTempDir;
}
private static File dump(InputStream input, File path,
String fileName) {
File file = new File(path, fileName);
try {
OutputStream output =
new FileOutputStream(file);
try {
int data;
while ((data = input.read()) != -1)
output.write(data);
} finally {
output.close();
}
return file;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
Приведенный выше класс является примером. Вы можете улучшить его, проверяя, есть ли место на диске, что целевой каталог доступен для записи и т. Д. Затем не забудьте удалить файл после того, как плохая библиотека использовала содержимое файла.
Выводы
- В следующий раз, когда вы определяете интерфейс библиотеки и, по любой причине, вы импортируете java.io.File … подумайте об использовании (также) InputStream [2], пожалуйста.
- Если вы склонны написать в своей конфигурации «путь к файлу» ресурса, который вам нужно прочитать, подумайте о возможности использовать URL-адрес с протоколом file: /path/to/file.ext. Кстати, имейте в виду, что, к сожалению, вы не можете писать в URL-адрес Java независимо от протокола, но вы все равно можете получить путь к файлу из URL-адреса, используя схему URI «file» .
Заметки
[1] Приведенный выше пример не является хорошим интерфейсом объекта почтовой программы. Это просто для того, чтобы объяснить, почему указывать файл как вложение не очень хорошая идея.
[2] Приведенное выше обсуждение действительно не только для InputStream и OutputStream, но также для Reader и Writer. Вы должны использовать Reader / Writer для работы с текстовыми данными, а InputStream / OutputStream для работы с двоичными данными.
От http://en.newinstance.it/2010/09/10/using-files-in-your-interfaces-is-not-a-good-idea