Подтвердить что ты не робот

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?

4b9b3361

Ответ 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.