簡體   English   中英

Java ClassLoader:兩次加載同一個類

[英]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”不應該在類路徑上!

正如之前所說,

  1. 每個類加載器都會記住(緩存)之前加載過的類並且不會再次重新加載它——本質上每個類加載器都定義了一個命名空間。
  2. 子類加載器將類加載委托給父類加載器,即

Java 8 及之前

自定義類加載器 -> 應用類加載器 -> 擴展類加載器 -> 引導類加載器

爪哇 9+

自定義類加載器 -> 應用類加載器 -> 平台類加載器 -> 引導類加載器。

從上面我們可以得出結論,每個 Class 對象都由其完全限定的類名和加載器來標識,而不是定義它(也稱為已定義加載器)

Javadocs

每個 Class 對象都包含對定義它的 ClassLoader 的引用。

方法defineClass 將字節數組轉換為類Class 的實例。 可以使用 Class.newInstance 創建這個新定義的類的實例。

重新加載類的簡單解決方案是定義新的(例如UrlClassLoader )或您自己的自定義類加載器。 對於需要替代類的更復雜的場景,可以使用動態代理機制。

請參閱下面的簡單解決方案,我通過定義自定義類加載器來解決類似問題以重新加載相同的類。 本質 - 覆蓋父類加載器的findClass方法,然后從從文件系統讀取的字節加載類。

  1. MyClassLoader - 覆蓋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");
            }
        }
    }
  1. SimpleClass - 實驗主題 - ** 重要:用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;
    }
}
  1. SimpleInterface - 主題接口:將接口與實現分離以編譯和執行主題的輸出。

package com.example.classloader;

public interface SimpleInterface {

    long getCount();
}
  1. 驅動程序 - 執行以測試

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM