![](/img/trans.png)
[英]How to load same class by custom classloader twice in Maven project with JUnit
[英]Java ClassLoader: load same class twice
我有一個ClassLoader
,它從源文件加載JavaCompiler
編譯的類。 但是當我更改源文件、保存並重新編譯它時, ClassLoader
仍然加載該類的第一個版本。
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> compiledClass = cl.loadClass(stringClass);
我錯過了什么? 像一個 newInstance 什么的?
類加載器不能替換已經加載的類。 loadClass
將返回現有Class
實例的引用。
您必須實例化一個新的類加載器並使用它來加載新類。 然后,如果你想“替換”這個類,你必須扔掉這個類加載器並創建另一個新的類加載器。
回應您的評論:做類似的事情
ClassLoader cl = new UrlClassLoader(new URL[]{pathToClassAsUrl});
Class<?> compiledClass = cl.loadClass(stringClass);
該類加載器將使用“默認委托父類加載器”,您必須注意,該類(由其完全限定的類名標識)尚未加載且無法由該父類加載器加載。 所以“pathToClassAsUrl”不應該在類路徑上!
正如之前所說,
Java 8 及之前
自定義類加載器 -> 應用類加載器 -> 擴展類加載器 -> 引導類加載器
爪哇 9+
自定義類加載器 -> 應用類加載器 -> 平台類加載器 -> 引導類加載器。
從上面我們可以得出結論,每個 Class 對象都由其完全限定的類名和加載器來標識,而不是定義它(也稱為已定義加載器)
從Javadocs :
每個 Class 對象都包含對定義它的 ClassLoader 的引用。
方法defineClass 將字節數組轉換為類Class 的實例。 可以使用 Class.newInstance 創建這個新定義的類的實例。
重新加載類的簡單解決方案是定義新的(例如UrlClassLoader )或您自己的自定義類加載器。 對於需要替代類的更復雜的場景,可以使用動態代理機制。
請參閱下面的簡單解決方案,我通過定義自定義類加載器來解決類似問題以重新加載相同的類。 本質 - 覆蓋父類加載器的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
}
}
每次都必須加載一個新的 ClassLoader,或者每次都必須為類指定不同的名稱並通過接口訪問它。
例如
interface MyWorker {
public void work();
}
class Worker1 implement MyWorker {
public void work() { /* code */ }
}
class Worker2 implement MyWorker {
public void work() { /* different code */ }
}
我認為這個問題可能比其他答案所暗示的更基本。 很可能類加載器正在加載與您認為的文件不同的文件。 為了測試這個理論,刪除 .class 文件(不要重新編譯你的 .java 源代碼)並運行你的代碼。 你應該得到一個例外。
如果您沒有收到異常,那么顯然類加載器正在加載與您認為的不同的 .class 文件。 因此,搜索另一個具有相同名稱的 .class 文件的位置。 刪除該 .class 文件並重試。 繼續嘗試,直到找到實際正在加載的 .class 文件。 一旦你這樣做了,你就可以重新編譯你的代碼並手動將類文件放在正確的目錄中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.