Java8: использование методов по умолчанию
При написании класса утилиты crypto я столкнулся с проблемой со следующим методом:
public static void destroy(Key key) throws DestroyFailedException {
if(Destroyable.class.isInstance(key)) {
((Destroyable)key).destroy();
}
}
@Test
public void destroySecretKeySpec() {
byte[] rawKey = new byte[32];
new SecureRandom().nextBytes(rawKey);
try {
destroy(new SecretKeySpec(rawKey , "AES"));
} catch(DestroyFailedException e) {
Assert.fail();
}
}
В частном случае javax.crypto.spec.SecretKeySpec
приведенный выше метод будет отлично работать в java7
, потому что SecretKeySpec (javadocs 7) не реализует Разрушимый (javadocs 7)
Теперь с java8
был создан класс SecretKeySpec (javadocs 8) Разрушимый (javadocs 8) и метод Destroyable # destroy теперь default
, что соответствует этому
Способы по умолчанию позволяют добавлять новые функциональные возможности к интерфейсам ваших библиотек и обеспечивать двоичную совместимость с кодом, написанным для более старых версий этих интерфейсов.
тогда код компилируется без каких-либо проблем, несмотря на то, что сам класс ScretKeySpec
не был изменен, только интерфейс SecretKey был.
Проблема заключается в том, что в oracle jdk8
метод destroy
имеет следующую реализацию:
public default void destroy() throws DestroyFailedException {
throw new DestroyFailedException();
}
что приводит к исключению во время выполнения.
Таким образом, двоичная совместимость может быть не сломана, но существующий код был. Тест выше проходит с помощью java7
, но не с java8
Итак, мои вопросы:
-
Как вообще работать с методами по умолчанию, которые могут привести к исключениям - потому что они не реализованы или не поддерживаются - или неожиданное поведение во время выполнения? кроме делать
Method method = key.getClass().getMethod("destroy");
if(! method.isDefault()) {
((Destroyable)key).destroy();
}
который действителен только для java8 и может быть неправильным в будущих выпусках, поскольку метод по умолчанию может получить значимую реализацию.
-
Не было бы лучше оставить этот метод по умолчанию пустым, а не бросать исключение (что ИМО вводит в заблуждение, так как в стороне от законного вызова для уничтожения ничего не было предпринято для эффективного уничтожения ключа, UnsupportedOperationException было бы лучше, и вы сразу узнаете, что происходит)
-
Подходит ли мой подход (тип check/cast/call)
if(Destroyable.class.isInstance(key))
((Destroyable)key).destroy();
для определения того, нужно ли уничтожать или не ошибаться? Что было бы альтернативой?
-
Является ли это заблуждением или они просто забывают добавить значимую реализацию в ScretKeySpec
?
Ответы
Ответ 1
Это заблуждение или они просто забыли добавить значимую реализацию в SecretKeySpec?
Ну, они не забыли. SecretKeySpec
нужна реализация, но она еще не выполнена. См. Bug JDK-8008795. Извините, нет ETA, если это будет исправлено.
В идеале допустимые реализации destroy
были бы добавлены во время добавления метода по умолчанию, и интерфейс был модифицирован на существующие классы, но этого не произошло, вероятно, из-за планирования.
Понятие "двоичная совместимость" в приведенном вами учебнике является довольно строгим определением, которое используется в Java Language Specification, глава 13. В основном это касается правильных преобразований в классы библиотек, которые не вызывают загрузку классов или связывание ошибок во время выполнения, в сочетании с классами, скомпилированными в отношении более старых версий этих классов библиотек. Это противоречит несовместимости источника, что приводит к ошибкам во время компиляции и поведенческой несовместимости, что приводит к обычно нежелательным изменениям в работе среды выполнения. Такие, как бросание исключений, которые не были брошены раньше.
Это не должно свести к минимуму тот факт, что ваш код сломан. Это все еще несовместимость. (К сожалению.)
В качестве обходного пути вы можете добавить instanceof PrivateKey || instanceof SecretKey
(так как это, по-видимому, классы, которые не имеют реализаций destroy
), и пусть тест утверждает, что они делают throw DestroyFailedException
, иначе if instanceof Destroyable
выполняет оставшуюся часть логики в вашем тесте. Тест снова завершится неудачно, когда эти экземпляры получат разумные реализации destroy
в некоторой будущей версии Java; это будет сигнал, чтобы изменить тест обратно на вызов destroy
на всех Destroyables. (Альтернативой может быть полное игнорирование этих классов, но тогда допустимые пути кода могут оказаться оставленными в течение некоторого времени.)
Ответ 2
Я только размышляю, но я думаю, что идея избавления исключения в реализации по умолчанию destroy
заключается в том, чтобы предупредить вас о том, что конфиденциальные данные не были уничтожены. Если реализация по умолчанию была пуста, и нет реализации, переопределяющей значение по умолчанию, вы можете ошибочно предположить, что конфиденциальные данные были уничтожены.
Я думаю, вы все равно должны поймать исключение DestroyFailedException
, независимо от того, выбрано ли оно из реализации по умолчанию или из реальной реализации, поскольку оно предупреждает вас, что ничего не было уничтожено, и вы должны решить, как справиться с этой ситуацией.
Контракт метода destroy
, который не изменился между Java 7 и Java 8 (помимо комментария о реализации по умолчанию) говорит - Sensitive information associated with this Object is destroyed or cleared. Subsequent calls to certain methods on this Object will result in an IllegalStateException being thrown.
и:
Выдает:
DestroyFailedException - если операция уничтожения не работает.
Если уничтожить не удастся, последующие вызовы определенным методам на этом объекте будут не приводить к срабатыванию IllegalStateException
. Это все еще верно, если уничтоженный ничего не сделал, и поэтому реализация по умолчанию (которая ничего не делает) бросает DestroyFailedException
.