0

抱歉,这是一些代码,但到目前为止它相当简单。

因此,我正在尝试动态执行类重新加载,因为类被修改和编译。我创建了一个类似于 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,它可以在这里找到

4

1 回答 1

0

有多少代码要调试!

我认为您违反的限制是java.lang.reflect.ProxyAPI 文档中的限制。

所有接口类型都必须通过指定的类加载器按名称可见。换句话说,对于类加载器cl和每个接口i,以下表达式必须为真:

    Class.forName(i.getName(), false, cl) == i

在行

Class<?> proxy = (Class<?>) Proxy.newProxyInstance(parent, new Class[] { _class}, handler);

parent应该替换为loader-ClassLoader加载界面的那个(假设_class是一个界面 - 我没有遵循它)。

于 2020-01-10T00:51:09.280 回答