java - 清除java中通过class.forName加载的类

标签 java swing user-interface classloader java-compiler-api

我创建了一个程序来加载用户选择的 java(JPanel) 文件。用户基本上选择一个由 JavaCompiler 编译的 Java 文件,然后加载下一个生成的类文件。 但是当通过一些文本编辑器在 java 文件 (JPanel) 中进行任何更改时,问题就来了,因为即使在关闭程序并重新运行项目后,任何新更改也不会反射(reflect)在类文件中。

我认为同一个类文件从内存中一次又一次地加载。

有什么方法可以从内存中清除加载的类吗?

编译:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler != null) {
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(diagnostics, null, null);
        Iterable<? extends JavaFileObject> fileObjects = stdFileManager.getJavaFileObjectsFromFiles(filesToCompile);
        List<String> optionList = new ArrayList<String>();
        // set compiler's classpath to be same as the runtime's
        rootDir=Utility.createRootDir();
        optionList.addAll(Arrays.asList("-d", rootDir.getAbsolutePath(), "-classpath", System.getProperty("java.class.path")));
        // optionList.add(()
        try {
            stdFileManager.flush();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        CompilationTask task = compiler.getTask(null, stdFileManager,null, optionList, null, fileObjects);
        Boolean result = task.call();
        try {
            stdFileManager.flush();
            stdFileManager.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
}

正在加载:

loader = new URLClassLoader(new URL[] { rootDir.toURI().toURL() });
cls = Class.forName(Utility.extractFQDN(sourceFile)+"."+Utility.extractClassName(sourceFile),true, loader); 

panel= (JPanel) cls.newInstance();

我已经用反编译器检查了编译后的类文件,它有更新的代码,但我不知道为什么以前的类文件被类加载器从内存中加载。

最佳答案

编辑:

这是一个 SSCCE,将字符串重复编译为相同的类名并演示新行为。为了避免文件困惑,它会在内存中执行所有操作。我认为这应该很容易适应您的应用程序。

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;


public class Compile {
    static class OutFile extends SimpleJavaFileObject {
        private final ByteArrayOutputStream out = new ByteArrayOutputStream();

        OutFile(String name) {
            super(URI.create("memory:///" + name.replace('.','/') + Kind.CLASS.extension), Kind.CLASS);
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            return out;
        }
    }

    static class InFile extends SimpleJavaFileObject {
        final String code;

        InFile(String name, String code) {
            super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    }

    static class Loader extends ClassLoader {
        private final Map<String, OutFile> files = new HashMap<String, OutFile>();

        public OutFile add(String className) {
            OutFile file = new OutFile(className);
            files.put(className, file);
            return file;
        }

        @Override
        protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class<?> c = findLoadedClass(name);
            if(c == null) {
                OutFile file = files.get(name);
                if(file == null) {
                    return super.loadClass(name, resolve);
                }

                c = defineClass(name, file.out.toByteArray(), 0, file.out.size());
            }

            if(resolve) {
                resolveClass(c);
            }

            return c;
        }
    }

    static class FileManager extends ForwardingJavaFileManager<JavaFileManager> {
        private final Loader loader = new Loader();

        protected FileManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        public Class<?> getClass(String name) throws ClassNotFoundException {
            return loader.loadClass(name);
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
            return loader.add(className);
        }
    }

    public static void compileAndRun(String source) throws Exception {
        InFile in = new InFile("Main", "class Main {\npublic static void main(String[] args) {\n" + source + "\n}\n}");

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        FileManager manager = new FileManager(compiler.getStandardFileManager(null, null, null));

        compiler.getTask(null, manager, null, null, null, Collections.singletonList(in)).call();

        Method method = manager.getClass("Main").getMethod("main", String[].class);
        method.setAccessible(true);
        method.invoke(null, (Object)new String[0]);
    }

    public static void main(String[] args) throws Exception {
        compileAndRun("System.out.println(\"Hello\");");
        compileAndRun("System.out.println(\"World\");");
    }
}

原文:

ClassLoader(以及像 URLClassLoader 这样的子类)将始终要求父类加载器加载类,如果有父类的话。如果在构造它时没有显式设置父级,则父级将设置为系统类加载器。因此,您创建的所有新类加载器都会延迟回系统类加载器,该类加载器已经定义了类。

要获得您想要的行为,请将父级设置为 null:

loader = new URLClassLoader(new URL[] { rootDir.toURI().toURL() }, null);

编辑:请注意,这只是一个问题,因为您将类编译到根目录,该目录也在系统类加载器的类路径中。如果您编译到某个临时目录,系统类加载器将无法找到该类,而 URL 加载器将自行加载该类。

关于java - 清除java中通过class.forName加载的类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14754509/

相关文章:

user-interface - 在类库中弹出对话框是个坏主意吗?

java - 使用 Spring 使用数据库中的多列数据填充下拉框不起作用

java改变面板视野

java - 调用 `setText()` 时 JTextfield 未更新

Java,确保用户没有在字符串中输入数字

javascript - 隐藏/显示元素和更改按钮

java - Gui 的按钮布局问题

java - Java中的无限循环

java - 读取文件列表<整数>

java - 在 Spring 3 中,我可以实例化一个具有建议抽象方法的抽象类 bean 吗?