Dynamically recompile and reload a class

2019-08-07 04:52发布

问题:

I'm building a server in java that can receive java source files, and it should dynamically compile it using JavaCompiler and then load the class. However the problem is that if the server receive a file with the same name but different content, it will still load the previous class and gave the same outputs. I've noticed some answers suggesting creating a superclass for the class I'm trying to load and using a different classLoader, but is it still the case if the java source file is dynamically sent to the server?

Here's my compile and load methods in FileServer.java:

public final static int FILE_SIZE = 1022386;

public static void compile(String fileName)
{
// Save source in .java file.
    File sourceFile = new File(fileName);

    // Compile source file.
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    DiagnosticCollector <JavaFileObject> diagnostics =
        new DiagnosticCollector<JavaFileObject>();
    StandardJavaFileManager fileManager = 
        compiler.getStandardFileManager(diagnostics, null, null);  
    File [] files = new File [] {sourceFile};
    Iterable<? extends JavaFileObject> compilationUnits =
        fileManager.getJavaFileObjectsFromFiles(Arrays.asList(files));

    String [] compileOptions = new String[] {"-classpath", "runtime.jar"};
    Iterable<String> compilationOptions = Arrays.asList(compileOptions);

    JavaCompiler.CompilationTask task =
        compiler.getTask(null, fileManager, diagnostics, compilationOptions, 
                null, compilationUnits);
    task.call();

}

public static void compileLoad (String fileName)
{
compile(fileName);

    String className = "";
    int i = 0;
    while(fileName.charAt(i) != '.') {
        className += fileName.charAt(i);
        i++;
    }

ClassLoader classLoader = FileServer.class.getClassLoader();
    // Dynamically load class and invoke its main method.
    try {
        //Class<?> cls = Class.forName(className);
    Class<?> cls = classLoader.loadClass(className);
        Method meth = cls.getMethod("main", String[].class);
        String[] params = null;
        meth.invoke(null, (Object) params);
    } catch (Exception e) {
        e.printStackTrace();     
    }
}

回答1:

The problem is that the normal behaviour for ClassLoader.loadClass or Class.forName is to return an existing Class if it was loaded previously. They won't look at the class file to see if it has changed.

(There is a good reason for this. Basically, the identity of an object type in Java is equivalent to a tuple consisting of the classes fully qualified name, and its class loader. This means that the JVM cannot (and will not) allow a classloader to "define" two classes with the same name. If you try to make this happen, I think that the defineClass method will just give you back the Class object for the previously loaded class.)

So ...

In order to achieve what you are trying to achieve, you need to create a new ClassLoader each time you want to load a new version of a class.