una objetos instanciar instancia dinamicamente crear como clases clase java java-9 dynamic-class-loaders jshell

java - objetos - Compartiendo clases cargadas dinĂ¡micamente con la instancia de JShell



instanciar clases dinamicamente java (2)

La solución es crear una implementación personalizada de LoaderDelegate , que proporcione instancias de clases ya cargadas en lugar de cargarlas de nuevo. Un ejemplo simple es usar la implementación predeterminada, DefaultLoaderDelegate ( source ) y anular el método findClass de su RemoteClassLoader interno

@Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] b = classObjects.get(name); if (b == null) { Class<?> c = null; try { c = Class.forName(name);//Use a custom way to load the class } catch(ClassNotFoundException e) { } if(c == null) { return super.findClass(name); } return c; } return super.defineClass(name, b, 0, b.length, (CodeSource) null); }

Para crear una instancia de JShell que funcione, use el siguiente código

JShell shell = JShell.builder() .executionEngine(new ExecutionControlProvider() { @Override public String name() { return "name"; } @Override public ExecutionControl generate(ExecutionEnv ee, Map<String, String> map) throws Throwable { return new DirectExecutionControl(new CustomLoaderDelegate()); } }, null) .build(); shell.addToClasspath("Example.jar");//Add custom classes to Classpath, otherwise they can not be referenced in the JShell

Por favor, vea las ediciones a continuación

Estoy tratando de crear una instancia de JShell que me dé acceso y me permita interactuar con los objetos en la JVM en la que se creó. Esto funciona bien con las clases que han estado disponibles en tiempo de compilación, pero falla para las clases que se cargan dinámicamente .

public class Main { public static final int A = 1; public static Main M; public static void main(String[] args) throws Exception { M = new Main(); ClassLoader cl = new URLClassLoader(new URL[]{new File("Example.jar").toURL()}, Main.class.getClassLoader()); Class<?> bc = cl.loadClass("com.example.test.Dynamic");//Works JShell shell = JShell.builder() .executionEngine(new ExecutionControlProvider() { @Override public String name() { return "direct"; } @Override public ExecutionControl generate(ExecutionEnv ee, Map<String, String> map) throws Throwable { return new DirectExecutionControl(); } }, null) .build(); shell.eval("System.out.println(com.example.test.Main.A);");//Always works shell.eval("System.out.println(com.example.test.Main.M);");//Fails (is null) if executionEngine is not set shell.eval("System.out.println(com.example.test.Dynamic.class);");//Always fails } }

Además, el intercambio de DirectExecutionControl con LocalExecutionControl da los mismos resultados, pero no entiendo la diferencia entre las dos clases.

¿Cómo puedo hacer que las clases que se cargan en tiempo de ejecución estén disponibles para esta instancia de JShell ?

Edición: la primera parte de esta pregunta se resolvió, a continuación se encuentra el código fuente actualizado para demostrar la segunda parte del problema

public class Main { public static void main(String[] args) throws Exception { ClassLoader cl = new URLClassLoader(new URL[]{new File("Example.jar").toURL()}, Main.class.getClassLoader()); Class<?> c = cl.loadClass("com.example.test.C"); c.getDeclaredField("C").set(null, "initial"); JShell shell = JShell.builder() .executionEngine(new ExecutionControlProvider() { @Override public String name() { return "direct"; } @Override public ExecutionControl generate(ExecutionEnv ee, Map<String, String> map) throws Throwable { return new DirectExecutionControl(); } }, null) .build(); shell.addToClasspath("Example.jar"); shell.eval("import com.example.test.C;"); shell.eval("System.out.println(C.C)"); //null shell.eval("C.C = /"modified/";"); shell.eval("System.out.println(C.C)"); //"modified" System.out.println(c.getDeclaredField("C").get(null)); //"initial" } }

Este es el resultado esperado, si la JVM y la instancia de JShell no comparten ninguna memoria, sin embargo, agregar com.example.test.C directamente al proyecto en lugar de cargarlo dinámicamente cambia los resultados de la siguiente manera:

shell.eval("import com.example.test.C;"); shell.eval("System.out.println(C.C)"); //"initial" shell.eval("C.C = /"modified/";"); shell.eval("System.out.println(C.C)"); //"modified" System.out.println(c.getDeclaredField("C").get(null)); //"modified"

¿Por qué no se comparte la memoria entre la JVM y la instancia de JShell para las clases cargadas en tiempo de ejecución?

EDIT 2: el problema parece ser causado por diferentes cargadores de clases

Ejecutando el siguiente código en el contexto del ejemplo anterior:

System.out.println(c.getClassLoader()); //java.net.URLClassLoader shell.eval("System.out.println(C.class.getClassLoader())"); //jdk.jshell.execution.DefaultLoaderDelegate$RemoteClassLoader shell.eval("System.out.println(com.example.test.Main.class.getClassLoader())"); //jdk.internal.loader.ClassLoaders$AppClassLoader

Esto muestra que la misma clase, com.example.test.C está cargada por dos cargadores de clases diferentes. ¿Es posible agregar la clase a la instancia de JShell sin volver a cargarla? Si no, ¿por qué la clase cargada estáticamente ya está cargada?


Sólo hablé con una pequeña parte de esta pregunta bastante importante:

Además, el intercambio de DirectExecutionControl con LocalExecutionControl da los mismos resultados, pero no entiendo la diferencia entre las dos clases

LocalExecutionControl extends DirectExecutionControl y reemplaza solo invoke(Method method) , cuyos cuerpos son ...

local:

Thread snippetThread = new Thread(execThreadGroup, () -> { ... res[0] = doitMethod.invoke(null, new Object[0]); ... });

directo:

Object res = doitMethod.invoke(null, new Object[0]);

así que la diferencia entre las dos clases es que invoca directamente el método en el subproceso actual, y local lo invoca en un nuevo subproceso. el mismo cargador de clases se usa en ambos casos, por lo que esperaría los mismos resultados en términos de compartir memoria y clases cargadas