¿Cómo resolver InaccessibleObjectException ("No se puede hacer accesible a {miembro}: el módulo {A} no 'abre {paquete}' en {B}") en Java 9?

Resuelto Nicolai Parlog asked hace 7 años • 23 respuestas

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 esto. 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 java.lang.Class final protegido java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) arroja java.lang.ClassFormatError accesible: módulo java.base no "abre java.lang" en el módulo sin nombre @1941a8ff

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

Nicolai Parlog avatar Dec 21 '16 21:12 Nicolai Parlog
Aceptado

La excepción la causa el Java Platform Module System que se introdujo en Java 9, particularmente su implementación de encapsulación fuerte. Sólo permite el acceso bajo ciertas condiciones, las más destacadas son:

  • el tipo tiene que ser publico
  • el paquete propietario debe exportarse

Las mismas limitaciones se aplican a la reflexión, que el código que causa la excepción intentó utilizar. 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 tienen javassist.util.proxy.SecurityActionsel 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, se debe convencer al sistema del módulo para que permita el acceso al elemento al que setAccessiblese llamó. Toda la información necesaria para ello 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 accesible a {miembro}: el módulo {A} no 'abre {paquete}' en {B}

Con diferencia, 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.)
    • {member}y {package}son partes de la API de Java
    • {B}es una biblioteca, marco o módulo de aplicación; a menudounnamed module @...
  2. Una biblioteca/marco basado en 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 de marco ounnamed 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 al JDK

Los módulos JDK son inmutables para los desarrolladores de aplicaciones por lo que no podemos cambiar sus propiedades. Esto deja sólo 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 (abreviado)...

No se puede hacer accesible java.lang.ClassLoader.defineClass: el módulo java.base no "abre java.lang" en el 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 reflectante está en un módulo con nombre, ALL-UNNAMEDse puede reemplazar por su nombre.

Tenga en cuenta que a veces puede resultar 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 generada por la herramienta de compilación.

Si hay demasiados indicadores para agregar, podría considerar usar el interruptor de apagado de encapsulación --permit-illegal-access . Permitirá que todo el código en la ruta de clase refleje los módulos con nombre en general. Tenga en cuenta que esta bandera sólo funcionará en Java 9 .

2. Reflexión sobre el código de la aplicación

En este escenario, es probable que pueda editar el módulo en el que se utiliza la reflexión para ingresar. (Si no, estás efectivamente en el caso 1). Eso significa que los indicadores de la línea de comandos no son necesarios y, en su lugar, {A}se puede usar el descriptor del módulo 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
  • exporte 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 para{B}
  • abra el paquete con opens {package}, lo que lo hace disponible en tiempo de ejecución (con o sin reflexión) para todo el código
  • abra el paquete al módulo de acceso con opens {package} to {B}, lo que lo hace disponible en tiempo de ejecución (con o sin reflexión) pero solo para{B}
  • abra el módulo completo con open module {A} { ... }, lo que hace que todos sus paquetes estén disponibles en tiempo de ejecución (con o sin reflexión) para todo el código

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

Nicolai Parlog avatar Dec 21 '2016 14:12 Nicolai Parlog

Solo un comentario reciente

Muchas propuestas para solucionar este problema tienen que ver con la opción vm launcher --illegal-access.

Según Oracle, con JEP 403 (enlace1) y JEP 403 (enlace2) que se decidió entregar desde JDK 17 en adelante , ¡la opción del iniciador --illegal-accessdejará de funcionar!

Resumen Encapsule firmemente todos los elementos internos del JDK, excepto las API internas críticas como sun.misc.Unsafe. Ya no será posible relajar la fuerte encapsulación de elementos internos a través de una única opción de línea de comando, como era posible en JDK 9 a JDK 16.

Y

Con este cambio, los usuarios finales ya no podrán utilizar la opción --illegal-access para permitir el acceso a elementos internos del JDK. (Una lista de los paquetes afectados está disponible aquí). Los paquetes sun.misc y sun.reflect aún serán exportados por el módulo jdk.unsupported y seguirán abiertos para que el código pueda acceder a sus elementos no públicos a través de la reflexión. No se abrirá ningún otro paquete JDK de esta forma.

Aún será posible utilizar la opción de línea de comandos --add-opens , o el atributo de manifiesto de archivo JAR Add-Opens, para abrir paquetes específicos.

Entonces la siguiente solución seguirá funcionando.

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

Pero la solución --illegal-accessdejará de funcionar a partir JDK 17de ahora.

Panagiotis Bougioukos avatar Sep 13 '2021 09:09 Panagiotis Bougioukos

Todavía es posible intentarlo con una versión anterior de JKD.

Para Eclipse necesitas hacer 2 cosas. Ir a

  1. Ventana -> Preferencia -> java -> Compilador Establecer el nivel de cumplimiento del compilador en una versión específica En mi caso, la versión de Eclipse estaba configurada en JDK 16. La revertí a 1.8 ya que mi código fue escrito en 1.8

  2. Ventana -> Preferencias -> Java -> JRE instalados. Agregue la ruta de instalación de JRE ( seleccione VM estándar )

A mi me funciono sin problemas..

Sudarshan Gaikwad avatar Sep 24 '2021 05:09 Sudarshan Gaikwad