Очистить класс, загруженный через class.forName в Java
Я создал программу, которая загружает файл Java(JPanel), который выбирает пользователь. Пользователь в основном выбирает файл Java, который компилируется JavaCompiler, и загружается следующий сгенерированный файл класса. Но проблема возникает, когда какие-либо изменения вносятся в java-файл (JPanel) через какой-либо текстовый редактор, поскольку любые новые изменения не отражаются в файле класса даже после закрытия программы и повторного запуска проекта.
Я думаю, один и тот же файл класса загружается снова и снова из памяти.
Есть ли способ очистить загруженный класс из памяти?
Компиляция:
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();
Я проверил скомпилированный файл класса с помощью декомпилятора, он обновил код, но я не знаю, почему предыдущий файл класса загружается из памяти загрузчиком классов.
1 ответ
Редактировать:
Вот 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 загрузит сам класс.