抱歉,这是一些代码,但到目前为止它相当简单。
因此,我正在尝试动态执行类重新加载,因为类被修改和编译。我创建了一个类似于 WatchDir 示例的 JavaSystemCompiler 类,除了每次保存 .java 文件时它只编译一次。为了实际执行加载和重新加载。一切正常,直到我尝试使用代理动态链接新类,在本例中为 RunContinually.java 和 RunContinuallyI.java。我得到的错误是:
线程“main”java.lang.IllegalArgumentException 中的异常:类 RunContinuallyImpl 在类加载器中不可见
我已经尝试了所有四种组合:
JavaSystemClassLoader.class.getClassLoader().getParent();
JavaSystemClassLoader.class.getClassLoader();
JSCL_2.class.getClassLoader();
and
JavaSystemClassLoader.class.getClassLoader().getSystemClassLoader();
它们都不起作用。下面列出了可编译的 .java 文件:
public interface RunContinuallyI {
public abstract void printHobby();
}
public class RunContinuallyImpl extends Thread implements RunContinuallyI {
public static int runNum = 0;
public RunContinuallyImpl() {
}
public void run() {
while(true) {
printHobby();
try {
sleep(5000);
} catch (InterruptedException ie) {
}
}
}
public void printHobby() {
System.out.println(++runNum + ": Compiling");
}
public static void main(String[] args) {
new RunContinuallyImpl().start();
}
}
import ca.tecreations.Global;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Iterator;
import java.util.stream.Collectors;
import org.apache.commons.io.*;
/**
*
* @author Tim, with starter source from Advanced Java Class Tutorial: A Guide to Class Reloading
*
* https://www.toptal.com/java/java-wizardry-101-a-guide-to-java-class-reloading
*
*/
public class JSCL_2 extends ClassLoader {
private static final String className = JSCL_2.class.getName();
ClassLoader parent;
JSCL_2 loader;
Class<?> _class;
byte[] data;
public JSCL_2(ClassLoader parent) {
super(parent);
this.parent = parent;
loader = this;
// System.out.println("Parent : " + parent.getParent());
// System.out.println("ParentClassLoader: " + parent.toString());
}
public Class<?> loadClass(String name) {
if (name.startsWith("java.") | name.startsWith("javax.")) {
try {
return parent.loadClass(name);
} catch (ClassNotFoundException cnfe) {
System.out.println("Class Not Found: " + name);
}
} else {
String path = Global.getProjectPath() + name.replace(".",File.separator) + ".class";
//System.out.println("" + className + ".loadClass : " + path + "," + name);
data = loadClassData(path);
if (data != null) {
try {
_class = defineClass(name, data, 0, data.length);
resolveClass(_class);
return _class;
} catch (Error e) {
System.out.println("Error while resolving: " + name);
e.printStackTrace();
}
}
}
return null;
}
public Class<?> reload(String path, String name) {
try {
new Thread().sleep(2500);
} catch (InterruptedException ie) {
System.out.println("Interrupted.");
}
System.out.println("" + className + ".reload: " + name);
data = loadClassData(path);
if (data != null) {
try {
_class = defineClass(name, data, 0, data.length);
resolveClass(_class);
// is this where you use the proxy????
// or is it better off in JavaSystemClassLoader.java?
return _class;
} catch (Error e) {
System.out.println("Error: " + e.toString());
}
}
return null;
}
protected byte[] loadClassData(String path) {
FileInputStream in = null;
try {
in = new FileInputStream(path);
} catch (FileNotFoundException fnfe) {
// So using this package, that shouldn't happend, but anyway
System.out.println(className + ".loadClassData: File not found: " + path);
}
// okay, so load the class
byte[] data = null;
try {
data = IOUtils.toByteArray(in);
in.close();
} catch (IOException ioe) {
System.err.println("IOException: reading class data.");
}
return data;
}
}
import ca.tecreations.*;
import java.awt.BorderLayout;
import java.awt.event.*;
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import static java.nio.file.LinkOption.*;
import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Collectors;
import javax.swing.*;
public class JavaSystemClassLoader extends TFrame implements ActionListener {
boolean _switch = true; // start with true, switch off, then on
public static final String className = JavaSystemClassLoader.class.getName();
public static final long buildNumber = 0L;
String classpath;
DefaultListModel<String> model = new DefaultListModel<>();
JList<String> list = new JList<String>(model);
JButton clear = new JButton("Clear");
private WatchService watcher = null;
private Map<WatchKey,Path> keys = null;
private boolean trace = false;
boolean debugDirs = false;
List<String> loadedClasses = new ArrayList<String>();
List<JSCL_2> loaders = new ArrayList<JSCL_2>();
List<String> unavaiClasses = new ArrayList<String>();
private ClassLoader parent = JavaSystemClassLoader.class.getClassLoader();
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
/**
* Register the given directory with the WatchService
*/
private void register(Path dir) {
WatchKey key = null;
loadDir(dir.toAbsolutePath().toString());
try {
key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
} catch (IOException ioe) {
if (trace) System.err.println("Skipping: " + dir.toString());
}
if (key != null) {
Path prev = keys.get(key);
if (prev == null) {
if (trace) System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
if (trace) System.out.format("update: %s -> %s\n", prev, dir);
//model.addElement("dir.rename: " + prev + "," + dir);
}
}
keys.put(key, dir);
}
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(final Path start) {
// register directory and sub-directories
File[] roots = start.toFile().listFiles();
if (roots != null) {
for(int i = 0; i < roots.length;i++) {
// exclude the one's giving errors
// watch, this happens on windows 7 at least,
if (roots[i].isDirectory()) {
if (
!roots[i].getAbsolutePath().toLowerCase().endsWith("config.msi") &&
// now, i like comments in between, so
!roots[i].getAbsolutePath().equals("/System/Library/DirectoryServices/DefaultLocalDB/Default")
) {
//so we excluded those, now
register(Paths.get(roots[i].getAbsolutePath()));
registerAll(Paths.get(roots[i].getAbsolutePath()));
//model.addElement(roots[i].getAbsolutePath());
}
}
}
}
}
/**
* Creates a WatchService and registers the given directory
*/
public JavaSystemClassLoader(String classpath) {
super(className);
this.classpath = classpath;
setTitle("Java System Class Loader");
try {
watcher = FileSystems.getDefault().newWatchService();
keys = new HashMap<WatchKey,Path>();
} catch (IOException ioe) {
System.out.println("IOE: " + ioe);
}
if (getStorage().wasCreated()) {
setSize(640,480);
setLocationRelativeTo(null);
}
add(new JScrollPane(list,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS),BorderLayout.CENTER);
JPanel panel = new JPanel();
panel.add(clear);
add(panel,BorderLayout.SOUTH);
clear.addActionListener(this);
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(clear)) {
model.removeAllElements();
}
}
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
System.exit(0);
}
/**
* Process all events for keys queued to the watcher
*/
void processEvents() {
for (;;) {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
System.err.println("WatchKey not recognized!!: " + key.toString());
continue;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
if (kind == OVERFLOW) {
System.out.println(className + ": KEY: OVERFLOW");
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> ev = cast(event);
Path name = ev.context();
Path child = dir.resolve(name);
// print out event
//if (child.toString().toLowerCase().endsWith(".class")) {
// System.out.format("%s: %s\n", event.kind().name(), child);
//}
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (kind == ENTRY_CREATE) {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
registerAll(child);
}
}
// on create compile. if .java
if (event.kind().name().equals("ENTRY_CREATE")) {
if (Files.isDirectory(child,NOFOLLOW_LINKS)) {
//if (debugDirs) model.addElement("dir.create: " + child + File.separator);
} else {
if (_switch && child.toAbsolutePath().toString().toLowerCase().endsWith(".class")) {
//model.addElement("file.create: " + child);
//model.addElement("Load : " + child.toString());
//System.out.println("Load : " + child.toString());
String className = getClassNameFromClassFilename(Global.getProjectPath(),child.toAbsolutePath().toString());
//model.addElement("Class : " + className);
load(child.toString(),className);
}
}
} else if (event.kind().name().equals("ENTRY_DELETE")) {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
//model.addElement("dir.delete: " + child + File.separator);
} else {
if (_switch && child.toString().endsWith(".class")) {
System.out.println("Deleted: " + child.toString());
}
}
} else if (event.kind().name().equals("ENTRY_MODIFY")) {
if (Files.isDirectory(child,NOFOLLOW_LINKS)) {
//model.addElement("dir.modify: " + child);
} else {
if (_switch && child.toString().toLowerCase().endsWith(".class")) {
//model.addElement("Reload: " + child.toString());
System.out.println("Reload: " + child.toString());
reload(child.toAbsolutePath().toString());
}
}
}
int lastIndex = list.getModel().getSize() - 1;
if (lastIndex >= 0) {
list.ensureIndexIsVisible(lastIndex);
}
// reset key and remove from set if directory no longer accessible
}
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
break;
}
}
_switch = !_switch;
}
}
public String getClassNameFromClassFilename(String classPath, String classFilename) {
String target = "";
if (classPath.endsWith(File.separator)) target = classFilename.substring(classPath.length());
else target = classFilename.substring(classPath.length() + 1);
target = target.substring(0,target.length() - 6); // remove .class
target = target.replace(File.separatorChar,'.'); // form filename to class name
return target;
}
public void printLoaded() {
java.util.List<String> loaded = loadedClasses.stream().sorted().collect(Collectors.toList());
for(int i = 0; i < loaded.size();i++) {
System.out.println("Loaded : " + loaded.get(i));
}
}
public void printUnavailable() {
java.util.List<String> unavailable = unavaiClasses.stream().sorted().collect(Collectors.toList());
for(int i = 0; i < unavailable.size();i++) {
System.out.println("Unavailable: " + unavailable.get(i));
}
}
public static void main(String[] args) {
// parse arguments
//if (args.length == 0 || args.length > 2)
// usage();
JavaSystemClassLoader loader = null;
loader = new JavaSystemClassLoader(Global.getProjectPath());
long start = Runtime.getRuntime().freeMemory();
loader.register(Paths.get(Global.getProjectPath()));
loader.registerAll(Paths.get(Global.getProjectPath()));
loader.printLoaded();
loader.printUnavailable();
long finished = Runtime.getRuntime().freeMemory();
long total = start - finished;
System.out.println("Memory Consumed: " + getMB(total));
new RunContinuallyImpl().start();
loader.processEvents();
}
public static String getMB(long total) {
int megabytes = (int)(total / (1000 * 1000));
return (megabytes + " MB");
}
public void loadDir(String path) {
File[] f = new File(path).listFiles();
for(int i = 0; i < f.length;i++) {
if (f[i].isFile() && f[i].getAbsolutePath().toLowerCase().endsWith(".class")) {
load(f[i].getAbsolutePath(),getClassNameFromClassFilename(Global.getProjectPath(),f[i].getAbsolutePath()));
}
}
}
public void load(String fileName, String name) {
//System.out.println(className + ".load : " + fileName + " : " + name);
//try {
// Class<?> cls = Class.forName(className);
//} catch (ClassNotFoundException cnfe) {
// System.err.println("load : Class not found: " + className);
//}
JSCL_2 loader = new JSCL_2(parent);
Class<?> _class = null;
_class = loader.loadClass(name);
if (_class == null) {
model.addElement("Load : null: " + fileName + " , " + name);
unavaiClasses.add(name);
} else {
loadedClasses.add(name);
loaders.add(loader);
}
}
public void reload(String fileName) {
String name = getClassNameFromClassFilename(Global.getProjectPath(),fileName);
JSCL_2 loader = new JSCL_2(parent);
Class<?> _class = loader.reload(fileName,name);
if (_class == null) {
model.addElement("Reload: null: " + fileName + " , " + name);
// couldn't/wouldn't do it....
} else {
int found = unavaiClasses.indexOf(name);
if (found == -1) {
int index = loadedClasses.indexOf(name);
loaders.set(index, loader);
model.addElement("Reloaded: " + name);
// so is this where I need the proxy?
InvocationHandler handler = new DynaCodeInvocationHandler(loader);
Class<?> proxy = (Class<?>) Proxy.newProxyInstance(parent, new Class[] { _class}, handler);
} else {
System.err.println("This change necessitates an application restart.");
}
}
}
}
import ca.tecreations.Global;
//import ca.tecreations.apps.viewer.FileViewer;
import java.awt.*;
import java.awt.event.*;
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import static java.nio.file.LinkOption.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
/**
* Example to watch a directory (or tree) for changes to files.
*
*/
public class JavaSystemCompiler extends TFrame implements ActionListener {
private static boolean _switch = true; // start at true, switch off, then on.
public static final String className = "JavaSystemCompiler";
String classpath;
DefaultListModel<String> model = new DefaultListModel<>();
JList<String> list = new JList<String>(model);
JButton view = new JButton("View");
JButton clear = new JButton("Clear");
private WatchService watcher = null;
private Map<WatchKey,Path> keys = null;
private boolean trace = true;
boolean debugDirs = false;
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
/**
* Register the given directory with the WatchService
*/
private void register(Path dir) {
WatchKey key = null;
try {
key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
} catch (IOException ioe) {
System.err.println("Skipping: " + dir.toString());
}
if (key != null) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
//model.addElement("dir.rename: " + prev + "," + dir);
}
}
keys.put(key, dir);
}
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(final Path start) {
// register directory and sub-directories
File[] roots = start.toFile().listFiles();
if (roots != null) {
for(int i = 0; i < roots.length;i++) {
// exclude the one's giving errors
// watch, this happens on windows 7 at least,
if (roots[i].isDirectory()) {
if (
!roots[i].getAbsolutePath().toLowerCase().endsWith("config.msi") &&
// now, i like comments in between, so
!roots[i].getAbsolutePath().equals("/System/Library/DirectoryServices/DefaultLocalDB/Default")
) {
//so we excluded that one, now
register(Paths.get(roots[i].getAbsolutePath()));
registerAll(Paths.get(roots[i].getAbsolutePath()));
//model.addElement(roots[i].getAbsolutePath());
}
}
}
}
}
/**
* Creates a WatchService and registers the given directory
*/
public JavaSystemCompiler(String classpath) {
super(className);
this.classpath = classpath;
setTitle("Java System Compiler");
try {
watcher = FileSystems.getDefault().newWatchService();
keys = new HashMap<WatchKey,Path>();
} catch (IOException ioe) {
System.out.println("IOE: " + ioe);
}
// enable trace after initial registration
this.trace = true;
if (getStorage().wasCreated()) {
setSize(640,480);
setLocationRelativeTo(null);
}
add(new JScrollPane(list,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS),BorderLayout.CENTER);
JPanel panel = new JPanel();
panel.add(clear);
panel.add(view);
add(panel,BorderLayout.SOUTH);
clear.addActionListener(this);
view.addActionListener(this);
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(clear)) {
model.removeAllElements();
} else if (e.getSource().equals(view)) {
String s = list.getSelectedValue();
String filename = s.substring(s.indexOf(":") + 1).trim();
if (s.startsWith("file")) {
// if (new File(filename).exists()) new FileViewer(filename);
}
}
}
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
System.exit(0);
}
/**
* Process all events for keys queued to the watcher
*/
void processEvents() {
WatchKey key;
for (;;) {
// wait for key to be signalled
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
System.err.println("WatchKey not recognized!!: " + key.toString());
continue;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
if (kind == OVERFLOW) {
System.out.println(className + ": KEY: OVERFLOW");
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> ev = cast(event);
Path name = ev.context();
Path child = dir.resolve(name);
// print out event
if (child.toString().toLowerCase().endsWith(".java")) {
if (_switch) {
System.out.format("%s: %s\n", event.kind().name(), child);
}
}
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (kind == ENTRY_CREATE) {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
registerAll(child);
}
}
// on create compile. if .java
if (event.kind().name().equals("ENTRY_CREATE")) {
if (Files.isDirectory(child,NOFOLLOW_LINKS)) {
//if (debugDirs) model.addElement("dir.create: " + child + File.separator);
//register??
} else {
if (child.toAbsolutePath().toString().toLowerCase().endsWith(".java")) {
//if (!buildUpdate) {
if (_switch) {
//model.addElement("file.create: " + child);
model.addElement("onCreate: " + SystemTool.compile(Global.getProjectPath(),child.toString()));
}
//}
}
}
} else if (event.kind().name().equals("ENTRY_DELETE")) {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
//model.addElement("dir.delete: " + child + File.separator);
} else {
if (child.toString().endsWith(".java")) {
if (_switch) {
model.addElement("onDelete: " + getDeleteClassFile(child.toString()));
}
}
}
} else if (event.kind().name().equals("ENTRY_MODIFY")) {
if (Files.isDirectory(child,NOFOLLOW_LINKS)) {
//model.addElement("dir.modify: " + child);
} else {
if (child.toString().toLowerCase().endsWith(".java")) {
//System.out.println("Child: " + child.toString());
if (new File(child.toString()).exists()) {
if (_switch) {
model.addElement("compile: " + SystemTool.compile(Global.getProjectPath(), child.toString()));
}
}
}
}
}
int lastIndex = list.getModel().getSize() - 1;
if (lastIndex >= 0) {
list.ensureIndexIsVisible(lastIndex);
}
}
// reset key and remove from set if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
break;
}
}
_switch = !_switch;
}
}
public static String getDeleteClassFile(String filename) {
//so take the filename and modify to be .class file
filename = filename.substring(0,filename.lastIndexOf(".")) + ".class";
new File(filename).delete();
// return the result of wether it exists or not.
if (new File(filename).exists()) return filename + ": false";
else return filename + ": true";
}
public static void main(String[] args) {
// parse arguments
//if (args.length == 0 || args.length > 2)
// usage();
JavaSystemCompiler compiler = null;
compiler = new JavaSystemCompiler(Global.getProjectPath());
compiler.register(Paths.get(Global.getProjectPath()));
compiler.registerAll(Paths.get(Global.getProjectPath()));
compiler.processEvents();
}
}
Global.getProjectPath 只返回源文件和 .class 文件所在的路径。
而且我猜你需要 org.apache.commons.io.jar,它可以在这里找到。