sirve - java8: tratar con métodos por defecto
jlabel java (2)
Mientras escribía una clase de utilidad criptográfica me encontré con un problema con el siguiente método:
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();
}
}
En el caso particular de javax.crypto.spec.SecretKeySpec
el método anterior funcionaría bien en java7
porque SecretKeySpec (javadocs 7) no implementa Destroyable (javadocs 7)
Ahora, con java8
la clase SecretKeySpec (javadocs 8) se ha convertido en Destroyable (javadocs 8) y el método Destroyable#destroy ahora es el default
cual está bien de acuerdo con esta statement
Los métodos predeterminados le permiten agregar una nueva funcionalidad a las interfaces de sus bibliotecas y garantizar la compatibilidad binaria con el código escrito para versiones anteriores de esas interfaces.
luego el código se compila sin ningún problema a pesar del hecho de que la clase ScretKeySpec
no se ha cambiado, solo la interfaz SecretKey ha sido SecretKey .
El problema es que en oracle''s jdk8
el método destroy
tiene la siguiente implementación:
public default void destroy() throws DestroyFailedException {
throw new DestroyFailedException();
}
lo que conduce a una excepción en tiempo de ejecución.
Por lo tanto, la compatibilidad binaria podría no haberse roto, pero el código existente lo ha sido. La prueba anterior pasa con java7
pero no con java8
Así que mis preguntas son:
¿Cómo lidiar en general con los métodos predeterminados que podrían llevar a excepciones, porque no se implementaron o no se admitieron, o un comportamiento inesperado en el tiempo de ejecución? aparte de hacer
Method method = key.getClass().getMethod("destroy"); if(! method.isDefault()) { ((Destroyable)key).destroy(); }
que solo es válido para java8 y que podría no ser correcto en futuras versiones, ya que el método predeterminado podría obtener una implementación significativa.
¿No sería mejor dejar este método predeterminado vacío en lugar de lanzar una excepción (que IMO es engañosa ya que, aparte de la llamada legítima para destruir nada, se ha intentado destruir la clave de manera efectiva, una UnsupportedOperationException no UnsupportedOperationException hubiera sido una mejor opción y usted lo haría? saber al instante lo que está pasando)
Es mi enfoque con (tipo check / cast / call)
if(Destroyable.class.isInstance(key)) ((Destroyable)key).destroy();
¿Para determinar si destruir o no mal? ¿Cuál sería una alternativa?
¿Es esto un error o simplemente se olvidaron de agregar una implementación significativa en
ScretKeySpec
?
¿Es esto un error o simplemente se olvidaron de agregar una implementación significativa en SecretKeySpec?
Pues no lo olvidaron. SecretKeySpec
necesita una implementación, pero simplemente no se ha hecho todavía. Ver error JDK-8008795 . Lo sentimos, no hay ETA cuando esto se solucionará.
Idealmente, las implementaciones válidas de destroy
se habrían agregado en el momento en que se agregó el método predeterminado y la interfaz se adaptó a las clases existentes, pero no sucedió, probablemente debido a la programación.
La noción de "compatibilidad binaria" en el tutorial que citó es una definición bastante estricta, que es la utilizada por la Especificación del lenguaje Java, capítulo 13 . Básicamente, se trata de transformaciones válidas a clases de biblioteca que no causan errores de carga o vinculación de clases en tiempo de ejecución, cuando se combinan con clases compiladas contra versiones anteriores de esas clases de biblioteca. Esto contrasta con la incompatibilidad de la fuente, que causa errores en el tiempo de compilación, y la incompatibilidad del comportamiento, que generalmente provoca cambios no deseados en el comportamiento del sistema en tiempo de ejecución. Como lanzar excepciones que no fueron lanzadas antes.
Esto no es para minimizar el hecho de que su código se rompió. Sigue siendo una incompatibilidad. (Lo siento.)
Como solución alternativa, puede agregar instanceof PrivateKey || instanceof SecretKey
instanceof PrivateKey || instanceof SecretKey
(ya que aparentemente son las clases que carecen de implementaciones de destroy
) y hacen que la prueba afirme que arrojan DestroyFailedException
, de lo contrario, si instanceof Destroyable
ejecutará el resto de la lógica en su prueba. La prueba volverá a fallar cuando estas instancias obtengan implementaciones de destroy
razonables en alguna versión futura de Java; esta será una señal para volver a cambiar la prueba a llamar a destroy
en todos los Destroyables. (Una alternativa podría ser ignorar estas clases por completo, pero luego las rutas de código válidas podrían permanecer sin cobertura durante bastante tiempo).
Solo estoy especulando, pero creo que la idea detrás de lanzar una excepción en la implementación predeterminada de destroy
es alertarle de que los datos confidenciales no fueron destruidos. Si la implementación predeterminada estaba vacía y no hay ninguna implementación que sustituya a la predeterminada, puede suponer por error que los datos confidenciales se destruyeron.
Creo que debería capturar la excepción DestroyFailedException
todos modos, independientemente de que se haya generado desde la implementación predeterminada o desde una implementación real, ya que le advierte que no se destruyó nada y debe decidir cómo manejar esta situación.
El contrato del método de destroy
, que no ha cambiado entre Java 7 y Java 8 (aparte del comentario sobre la implementación predeterminada) dice: 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.
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.
y:
Tiros
DestroyFailedException - si la operación de destrucción falla.
Si la destrucción falla, las llamadas subsiguientes a ciertos métodos en este Objeto no darán como resultado una IllegalStateException
. Eso sigue siendo cierto si se destruye no hizo nada y, por lo tanto, la implementación predeterminada (que no hace nada) lanza DestroyFailedException
.