我有一个ClassLoader
,它可以从源文件中通过JavaCompiler
编译加载一个类。
但是当我修改源文件并重新编译后,ClassLoader
仍然会加载第一个版本的类。
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> compiledClass = cl.loadClass(stringClass);
我缺少什么?比如newInstance还是其他什么?
我有一个ClassLoader
,它可以从源文件中通过JavaCompiler
编译加载一个类。
但是当我修改源文件并重新编译后,ClassLoader
仍然会加载第一个版本的类。
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> compiledClass = cl.loadClass(stringClass);
ClassLoader cl = new UrlClassLoader(new URL[]{pathToClassAsUrl});
Class<?> compiledClass = cl.loadClass(stringClass);
这个类加载器将使用"默认委托父类加载器",你需要注意的是,该类(通过完全限定的类名识别)尚未被加载,也不能通过该父类加载器加载。因此,“pathToClassAsUrl”不应该在类路径上!
每次都必须加载一个新的ClassLoader,或者每次都必须给类取一个不同的名称,并通过接口访问它。
例如:
interface MyWorker {
public void work();
}
class Worker1 implement MyWorker {
public void work() { /* code */ }
}
class Worker2 implement MyWorker {
public void work() { /* different code */ }
}
new ClassLoader()
,并通过调用 defineClass 来触发它加载类。 - Peter Lawrey正如之前所述,
Java 8及之前版本
自定义类加载器 -> 应用程序类加载器 -> 扩展类加载器 -> 引导类加载器
Java 9+
自定义类加载器 -> 应用程序类加载器 -> 平台类加载器 -> 引导类加载器。
由此可见,每个Class对象都通过其完全限定的类名和定义它的加载器(也称为已定义的加载器)进行标识。
从Javadocs中可以得出:
简单的解决方案是定义一个新的类加载器(例如UrlClassLoader),或者自己创建一个自定义的类加载器来重新加载类。 对于更复杂的情况,需要替换类时,可以利用动态代理机制。每个Class对象都包含对定义它的ClassLoader的引用。
defineClass方法将字节数组转换为Class类的实例。可以使用Class.newInstance创建这个新定义的类的实例。
findClass
方法,然后从文件系统中读取字节加载类。
findClass
并执行defineClass
package com.example.classloader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader {
private String classFileLocation;
public MyClassLoader(String classFileLocation) {
this.classFileLocation = classFileLocation;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = loadClassBytesFromDisk(classFileLocation);
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte [] loadClassBytesFromDisk(String classFileLocation) {
try {
return Files.readAllBytes(Paths.get(classFileLocation));
}
catch (IOException e) {
throw new RuntimeException("Unable to read file from disk");
}
}
}
javac
编译后,将 SimpleClass.java 从类路径中移除(或重命名)
否则由于类加载委托机制,它将被系统类加载器加载。**
来自 src/main/java
javac com/example/classloader/SimpleClass.java
package com.example.classloader;
public class SimpleClassRenamed implements SimpleInterface {
private static long count;
public SimpleClassRenamed() {
count++;
}
@Override
public long getCount() {
return count;
}
}
package com.example.classloader;
public interface SimpleInterface {
long getCount();
}
package com.example.classloader;
import java.lang.reflect.InvocationTargetException;
public class MyClassLoaderTest {
private static final String path = "src/main/java/com/example/classloader/SimpleClass.class";
private static final String className = "com.example.classloader.SimpleClass";
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException { // Exception hell due to reflection, sorry :)
MyClassLoader classLoaderOne = new MyClassLoader(path);
Class<?> classOne = classLoaderOne.loadClass(className);
// we need to instantiate object using reflection,
// otherwise if we use `new` the Class will be loaded by the System Class Loader
SimpleInterface objectOne =
(SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// trying to re-load the same class using same class loader
classOne = classLoaderOne.loadClass(className);
SimpleInterface objectOneReloaded = (SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// new class loader
MyClassLoader classLoaderTwo = new MyClassLoader(path);
Class<?> classTwo = classLoaderTwo.loadClass(className);
SimpleInterface ObjectTwo = (SimpleInterface) classTwo.getDeclaredConstructor().newInstance();
System.out.println(objectOne.getCount()); // Outputs 2 - as it is the same instance
System.out.println(objectOneReloaded.getCount()); // Outputs 2 - as it is the same instance
System.out.println(ObjectTwo.getCount()); // Outputs 1 - as it is a distinct new instance
}
}
我认为问题可能比其他答案提供的更基础。很有可能类加载器正在加载一个与您想象不同的文件。为了测试这个理论,请删除.class文件(不要重新编译.java源代码),然后运行您的代码。您应该会收到一个异常。
如果您没有收到异常,那么显然类加载器正在加载与您想象的不同的.class文件。因此,请搜索具有相同名称的另一个.class文件的位置。删除该.class文件,然后再尝试一次。不断尝试,直到找到实际被加载的.class文件。一旦找到,您就可以重新编译代码并手动将类文件放在正确的目录中。