使用JShell实例共享动态加载的类

Job*_*oba 24 java dynamic-class-loaders java-9 jshell

请查看以下编辑内容

我正在尝试创建一个JShell实例,它允许我访问,并允许我与它创建的JVM中的对象进行交互.这适用于在编译时可用的类但对于动态加载的类失败.

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

此外,交流DirectExecutionControlLocalExecutionControl给出相同的结果,但我不明白,这两个类之间的差别.

我如何使运行时加载的类可用于此JShell实例

编辑:这个问题的第一部分已经解决,下面是更新的源代码,以演示问题的第二部分

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

这是预期的输出,如果JVMJShell实例不共享任何内存,但是com.example.test.C直接添加到项目而不是动态加载它会更改结果,如下所示:

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

为什么JVMJShell实例之间的内存不为运行时加载的类共享?

编辑2:问题似乎是由不同的类加载器引起的

在上面的示例的上下文中执行以下代码:

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

这表明,同一个类com.example.test.C由两个不同的类加载器加载.是否可以将类添加到JShell实例而不再重新加载?如果不是,为什么已经加载了静态加载的类?

Job*_*oba 8

解决方案是创建一个自定义LoaderDelegate实现,它提供已加载类的实例,而不是再次加载它们.一个简单的例子是使用默认实现DefaultLoaderDelegate(source)并覆盖findClass其内部的方法RemoteClassLoader

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

要创建有效的JShell实例,请使用以下代码

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

  • 您可以将赏金奖励给自己吗? (2认同)
  • 据我所知,这是不可能的 (2认同)