java8:处理默认方法

2022-09-02 22:20:10

在编写加密实用程序类时,我遇到了以下方法的问题:

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();
    }
}

在上述方法的特定情况下,由于SecretKeySpec(javadocs 7)不实现Destroyable(javadocs 7),因此可以正常工作。javax.crypto.spec.SecretKeySpecjava7

现在,使用类SecretKeySpec(javadocs 8)已经变得可销毁(javadocs 8),并且方法Destroyable#destroy现在是,根据此语句,这很好java8default

默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性

然后代码编译没有任何问题,尽管类本身没有被更改,单独的接口SecretKey已经。ScretKeySpec

问题是,该方法中具有以下实现:oracle's jdk8destroy

public default void destroy() throws DestroyFailedException {
    throw new DestroyFailedException();
}

这会导致运行时出现异常。

因此,二进制兼容性可能没有被破坏,但现有的代码已经被破坏了。上述测试通过,但未通过java7java8

所以我的问题是:

  • 通常如何处理可能导致异常(因为未实现或不支持)或运行时意外行为的默认方法?除了做

    Method method = key.getClass().getMethod("destroy");
    if(! method.isDefault()) {
        ((Destroyable)key).destroy();
    }
    

    它只对java8有效,在将来的发行版中可能不正确,因为默认方法可能会得到一个有意义的实现。

  • 将此默认方法留空而不是抛出异常(IMO具有误导性,因为除了合法调用销毁任何东西都没有尝试有效地销毁密钥之外,不受支持的操作异常会更合适,您会立即知道发生了什么)不是更好吗?

  • 是我的方法(类型检查/投射/调用)

    if(Destroyable.class.isInstance(key))
        ((Destroyable)key).destroy();
    

    用于确定是否要破坏错误?有什么替代方案?

  • 这是一种误解,还是他们只是忘记在中添加有意义的实现?ScretKeySpec


答案 1

这是一种误解,还是他们只是忘记了在SecretKeySpec中添加有意义的实现?

好吧,他们没有忘记。 确实需要一个实现,但它还没有完成。请参阅错误 JDK-8008795。抱歉,没有关于何时修复此问题的ETA。SecretKeySpec

理想情况下,在添加默认方法时会添加 有效的实现,并且接口被重构到现有类上,但这并没有发生,可能是因为调度。destroy

您引用的教程中的“二进制兼容性”概念是一个相当严格的定义,这是Java语言规范第13章中使用的定义。基本上,它是关于对库类的有效转换,当与针对这些库类的旧版本编译的类结合使用时,这些库类在运行时不会导致类加载或链接错误。这与源代码不兼容(会导致编译时错误)和行为不兼容(导致系统运行时行为中通常不需要的更改)形成对比。例如,抛出以前未引发的异常。

这并不是要最大限度地减少代码被破坏的事实。这仍然是一个不兼容的问题。(抱歉。

作为一种解决方法,您可以添加(因为这些类显然是缺少实现的类)并让测试断言它们确实会抛出 ,否则如果执行测试中逻辑的其余部分。当这些实例在将来的Java版本中获得合理的实现时,测试将再次失败;这将是一个信号,将测试改回调用所有可销毁的。(另一种选择可能是完全忽略这些类,但随后有效的代码路径可能会在相当长的一段时间内保持未被发现状态。instanceof PrivateKey || instanceof SecretKeydestroyDestroyFailedExceptioninstanceof Destroyabledestroydestroy


答案 2

我只是推测,但我认为在 的默认实现中抛出异常背后的想法是提醒您敏感数据未被销毁。如果默认实现为空,并且没有实现覆盖默认实现,则可能会错误地认为敏感数据已被销毁。destroy

我认为无论如何你都应该捕获异常,不管它是从默认实现还是从实际实现中引发的,因为它警告你没有任何被破坏,你应该决定如何处理这种情况。DestroyFailedException

该方法的合约在Java 7和Java 8之间没有变化(除了关于默认实现的评论)说 -destroySensitive 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 - 如果销毁操作失败。

如果销毁失败,则对此对象上某些方法的后续调用会导致引发。如果销毁不执行任何操作,则仍然如此,因此默认实现(不执行任何操作)将抛出 。IllegalStateExceptionDestroyFailedException


推荐