java8:处理默认方法

A4L*_*A4L 18 java secret-key java-8 default-method

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

javax.crypto.spec.SecretKeySpec上述方法的特定情况下可以正常工作,java7因为SecretKeySpec(javadocs 7)没有实现Destroyable(javadocs 7)

现在使用java8SecretKeySpec(javadocs 8)已经被Destroyable(javadocs 8)和方法Destroyable#destroy现在default哪个很好,根据这个声明

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

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

问题是oracle's jdk8destroy方法有以下实现:

public default void destroy() throws DestroyFailedException {
    throw new DestroyFailedException();
}
Run Code Online (Sandbox Code Playgroud)

这导致运行时出现异常.

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

所以我的问题是:

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

    Method method = key.getClass().getMethod("destroy");
    if(! method.isDefault()) {
        ((Destroyable)key).destroy();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    它仅对java8有效,并且在将来的版本中可能不正确,因为默认方法可能会获得有意义的实现.

  • 将这个默认方法保留为空而不是抛出异常(IMO是误导性的,因为除了合法的调用之外没有任何东西试图有效地破坏密钥,这是不是更好),UnsupportedOperationException会更合适你会立即知道发生了什么)

  • 是我的方法(类型检查/演员/电话)

    if(Destroyable.class.isInstance(key))
        ((Destroyable)key).destroy();
    
    Run Code Online (Sandbox Code Playgroud)

    确定是否破坏?什么是另类?

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

Stu*_*rks 7

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

他们没有忘记.SecretKeySpec确实需要一个实现,但它还没有完成.请参阅错误JDK-8008795.对不起,修复时没有ETA.

理想情况下,destroy在添加默认方法并将接口改装到现有类时,会添加有效的实现,但这可能不会发生,可能是因为调度.

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

这不是为了尽量减少您的代码被破坏的事实.它仍然是不兼容的.(抱歉.)

作为一种解决方法,您可以添加instanceof PrivateKey || instanceof SecretKey(因为这些显然是缺少destroy实现的类)并且让测试断言它们会抛出DestroyFailedException,否则如果instanceof Destroyable执行测试中的其余逻辑.当这些实例destroy在Java的某些未来版本中得到合理的实现时,测试将再次失败; 这将是一个信号,可以将测试更改为调用destroy所有Destroyables.(另一种方法可能是完全忽略这些类,但是有效的代码路径最终可能会在很长一段时间内被忽略.)


Era*_*ran 5

我只是在推测,但我认为在默认实现中抛出异常的想法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 - 如果destroy操作失败.

如果destroy失败,则对此Object的某些方法的后续调用将不会导致IllegalStateException抛出.如果销毁什么都没做,那仍然是正确的,因此默认实现(什么都不做)抛出DestroyFailedException.

  • "在默认的destroy实现中抛出异常背后的想法是提醒你敏感数据没有被破坏"---但旧的状态,即类甚至没有声明自己可以破坏,似乎已经发出信号更好,在开发时.所以真正的问题是,为什么要对所有东西强制"毁灭",是否真的可以破坏? (2认同)