what tutorial modules info java reflection java-9

tutorial - what is java 9 module



¿Cómo resolver InaccessibleObjectException(“No se puede hacer que{member} sea accesible: el módulo{A} no ''abre{paquete}'' a{B}”) en Java 9? (4)

Esta excepción ocurre en una amplia variedad de escenarios cuando se ejecuta una aplicación en Java 9. Ciertas bibliotecas y marcos (Spring, Hibernate, JAXB) son particularmente propensos a ello. Aquí hay un ejemplo de Javassist:

java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @1941a8ff at java.base/jdk.internal.reflect.Reflection.throwInaccessibleObjectException(Reflection.java:427) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:201) at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:192) at java.base/java.lang.reflect.Method.setAccessible(Method.java:186) at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:102) at javassist.util.proxy.FactoryHelper.toClass2(FactoryHelper.java:180) at javassist.util.proxy.FactoryHelper.toClass(FactoryHelper.java:163) at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:501) at javassist.util.proxy.ProxyFactory.createClass2(ProxyFactory.java:486) at javassist.util.proxy.ProxyFactory.createClass1(ProxyFactory.java:422) at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.java:394)

El mensaje dice:

No se puede hacer que java.lang.Class java.lang.ClassLoader.defineClass final protegido (java.lang.String, byte [], int, int, java.security.ProtectionDomain) arroje java.lang.ClassFormatError accesible: módulo java.base no "abre java.lang" al módulo sin nombre @ 1941a8ff

¿Qué se puede hacer para evitar la excepción y hacer que el programa se ejecute correctamente?


El uso de --add-opens debe considerarse una solución alternativa. Lo correcto es que Spring, Hibernate y otras bibliotecas hagan acceso ilegal para solucionar sus problemas.


Este es un problema muy difícil de resolver; y como han señalado otros, la opción --add-opens es solo una solución alternativa. La urgencia de resolver los problemas subyacentes solo crecerá una vez que Java 9 esté disponible públicamente.

Me encontré en esta página después de recibir este error exacto de Javassist mientras probaba mi aplicación basada en Hibernate en Java 9. Y dado que mi objetivo es admitir Java 7, 8 y 9 en múltiples plataformas, luché por encontrar la mejor solución. (Tenga en cuenta que las JVM de Java 7 y 8 abortarán inmediatamente cuando vean un argumento "--add-opens" no reconocido en la línea de comandos; por lo tanto, esto no se puede resolver con cambios estáticos en archivos por lotes, scripts o accesos directos).

Sería bueno recibir orientación oficial de los autores de las bibliotecas principales (como Spring e Hibernate), pero a falta de 100 días para el lanzamiento actualmente proyectado de Java 9, ese consejo aún parece difícil de encontrar.

Después de mucha experimentación y pruebas, me sentí aliviado de encontrar una solución para Hibernate:

  1. Utilice Hibernate 5.0.0 o superior (las versiones anteriores no funcionarán) y
  2. Solicite la mejora del código de bytes en tiempo de compilación (utilizando los complementos Gradle, Maven o Ant).

Esto evita la necesidad de que Hibernate realice modificaciones de clase basadas en Javassist en tiempo de ejecución, eliminando el seguimiento de la pila que se muestra en la publicación original.

SIN EMBARGO , debe probar a fondo su aplicación después. Los cambios de código de bytes aplicados por Hibernate en el momento de la compilación parecen diferir de los aplicados en tiempo de ejecución, lo que provoca un comportamiento de aplicación ligeramente diferente. Las pruebas unitarias en mi aplicación que han tenido éxito durante años de repente fallaron cuando habilité la mejora del código de bytes de tiempo de compilación. (Tuve que perseguir nuevas LazyInitializationExceptions y otros problemas). Y el comportamiento parece variar de una versión de Hibernate a otra. Proceda con precaución.


La excepción es causada por el Java Platform Module System que se introdujo en Java 9, particularmente su implementación de encapsulación fuerte. Solo permite el access bajo ciertas condiciones, las más destacadas son:

  • el tipo tiene que ser público
  • el paquete propietario debe exportarse

Las mismas limitaciones son verdaderas para la reflexión, que el código que causó la excepción intentó usar. Más precisamente, la excepción es causada por una llamada a setAccessible . Esto se puede ver en el seguimiento de la pila anterior, donde las líneas correspondientes en javassist.util.proxy.SecurityActions tienen el siguiente aspecto:

static void setAccessible(final AccessibleObject ao, final boolean accessible) { if (System.getSecurityManager() == null) ao.setAccessible(accessible); // <~ Dragons else { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { ao.setAccessible(accessible); // <~ moar Dragons return null; } }); } }

Para asegurarse de que el programa se ejecute correctamente, el sistema de módulos debe estar convencido de permitir el acceso al elemento en el que se llamó a setAccessible . Toda la información requerida para eso está contenida en el mensaje de excepción, pero existen varios mecanismos para lograrlo. Cuál es el mejor depende del escenario exacto que lo causó.

No se puede hacer que {member} sea accesible: el módulo {A} no ''abre {paquete}'' a {B}

Con mucho, los escenarios más destacados son los dos siguientes:

  1. Una biblioteca o marco utiliza la reflexión para llamar a un módulo JDK. En este escenario:

    • {A} es un módulo Java (con el prefijo java. o jdk. ) jdk.
    • {member} y {package} son partes de la API de Java
    • {B} es una biblioteca, marco o módulo de aplicación; unnamed module @... menudo unnamed module @...
  2. Una biblioteca / marco basado en la reflexión como Spring, Hibernate, JAXB, ... refleja el código de la aplicación para acceder a beans, entidades, ... En este escenario:

    • {A} es un módulo de aplicación
    • {member} y {package} son parte del código de la aplicación
    • {B} es un módulo marco o un módulo unnamed module @...

Tenga en cuenta que algunas bibliotecas (JAXB, por ejemplo) pueden fallar en ambas cuentas, ¡así que observe de cerca en qué escenario se encuentra! El de la pregunta es el caso 1.

1. Llamada reflexiva a JDK

Los módulos JDK son inmutables para los desarrolladores de aplicaciones, por lo que no podemos cambiar sus propiedades. Esto deja solo una solución posible: banderas de línea de comando . Con ellos es posible abrir paquetes específicos para la reflexión.

Entonces, en un caso como el anterior (acortado) ...

No se puede hacer que java.lang.ClassLoader.defineClass sea accesible: el módulo java.base no "abre java.lang" al módulo sin nombre @ 1941a8ff

... la solución correcta es iniciar la JVM de la siguiente manera:

# --add-opens has the following syntax: {A}/{package}={B} java --add-opens java.base/java.lang=ALL-UNNAMED

Si el código reflejado está en un módulo con nombre, ALL-UNNAMED puede reemplazarse por su nombre.

Tenga en cuenta que a veces puede ser difícil encontrar una manera de aplicar este indicador a la JVM que realmente ejecutará el código reflejado. Esto puede ser particularmente difícil si el código en cuestión es parte del proceso de compilación del proyecto y se ejecuta en una JVM que generó la herramienta de compilación.

Si hay demasiados indicadores para agregar, puede considerar usar el interruptor de encapsulación kill --permit-illegal-access lugar. Permitirá que todo el código en la ruta de clase se refleje sobre todos los módulos con nombre. ¡Tenga en cuenta que esta bandera solo funcionará en Java 9 !

2. Reflexión sobre el código de solicitud

En este escenario, es probable que pueda editar el módulo en el que se utiliza la reflexión. (Si no, está efectivamente en el caso 1.) Eso significa que los indicadores de línea de comando no son necesarios y, en cambio, el descriptor del módulo {A} puede usarse para abrir sus componentes internos. Hay una variedad de opciones:

  • exportar el paquete con exports {package} , lo que lo hace disponible en tiempo de compilación y ejecución para todo el código
  • exportar el paquete al módulo de acceso con exports {package} to {B} , lo que lo hace disponible en tiempo de compilación y ejecución pero solo a {B}
  • abra el paquete con opens {package} , que lo hace disponible en tiempo de ejecución (con o sin reflejo) para todo el código
  • abra el paquete en el módulo de acceso con opens {package} to {B} , que lo hace disponible en tiempo de ejecución (con o sin reflejo) pero solo para {B}
  • abra todo el módulo con open module {A} { ... } , que hace que todos sus paquetes estén disponibles en tiempo de ejecución (con o sin reflexión) para todo el código

Vea esta publicación para una discusión más detallada y una comparación de estos enfoques.


Tuve advertencias con hibernate 5.

Illegal reflective access by javassist.util.proxy.SecurityActions

Agregué la última biblioteca javassist a las dependencias gradle:

compile group: ''org.javassist'', name: ''javassist'', version: ''3.22.0-GA''

Esto resolvió mi problema.