首頁  >  文章  >  Java  >  Java動態類別載入和重新載入的詳細介紹

Java動態類別載入和重新載入的詳細介紹

不言
不言轉載
2018-10-17 16:28:275109瀏覽

這篇文章帶給大家的內容是關於Java動態類別載入和重新載入的詳細介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

Java中可以在運行時載入和重新載入類,雖然並不像我們想像中那麼簡單。本文將解釋何時、怎樣在Java中載入、重新載入類別。
你可以爭論動態載入類別是Java反射的一部分還是Java核心的一部分。不管怎樣,我把它放在了Java反射中,因為沒有更好的地方放置它。

類別的載入器

Java程式的所有類別都是使用 java.lang.ClassLoader的一些子類別載入的。因此,動態載入類別也必須使用 java.lang.ClassLoader的子類別。
當一個類別載入,它所引用的類別也會被載入。類別載入模式是遞歸載入的,直到所有需要的類別載入完畢。這可能並不是應用程式的所有類別。未被引用的類別在引用前不會被載入。

類別載入層級結構

類別載入在Java中被組織成層級。當你建立一個獨立的ClassLoader,你必須提供一個父級ClassLoader。如果ClassLoader被要求載入一個類,它會請求它的父級ClassLoader去載入它。如果父級類別載入器找不到這個類,子類別載入器會嘗試自載入。

類別載入

類別載入器載入類別的步驟如下:

  1. #檢查該類別是否已載入

  2. #如類別未加載,請求父類別載入器載入它

  3. 如父類別載入器不能載入該類,嘗試使用目前類別載入器載入它

當你實作一個能夠重載類別的類別載入器時,你需要從這個序列中偏離一點。不應請求父類別載入程式載入要重裝的類別。稍後再談。

動態類別載入

動態載入類別非常簡單。你需要做的一切是取得一個ClassLoader並呼叫它的loadClass()方法。範例如下:

public class MainClass {

  public static void main(String[] args){

    ClassLoader classLoader = MainClass.class.getClassLoader();

    try {
        Class aClass = classLoader.loadClass("com.jenkov.MyClass");
        System.out.println("aClass.getName() = " + aClass.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

}

動態類別重新載入

動態類別重新載入有一些挑戰。 Java內建的類別載入器在載入類別之前總是會檢查類別是否已載入。因此,使用Java的內建類別載入器不可能重新載入類別。重新載入一個類別你必須實作自己的ClassLoader子類別。
即使使用類別載入器的自訂子類,也會遇到挑戰。所有已載入的類別都需要被連結。這個方法是final的,因此不能被你的ClassLoader子類別重載。 resolve()方法不允許ClassLoader實例連結一個類別2次。因此,每當你需要重新載入類別時,你必須重新建立一個ClassLoader類別的實例。這不是不可能的,但必須知道何時設計類別重新載入。

類別重載程式設計

如上文述,不能使用載入指定類別的ClassLoader重新載入這個類別。因此,必須使用不同的ClassLoader來載入這個類別。但是,這會帶來新的問題。
Java程式中載入的每一個類別都以其全限定名(包名 類別名稱)標識,並且由ClassLoader實例載入。這意味著,類別MyObject由類別載入器A載入,是和由類別載入器B載入的相同類別MyObject不相同。模擬程式碼如下:

MyObject object = (MyObject)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

注意,類別MyObject在程式碼中是如何被引用的,是作為object類型的變數。這導致MyObject類別被已載入過這個類別的駐留程式碼的類別載入器載入。
如果myClassReloadingFactory物件工廠使用與駐留程式碼不同的類別載入器來載入MyObject,你不能強制轉換重新載入的Object類型的變數MyObject為MyObject類型。因為這兩個MyObject由不同的類別載入器加載,他們被視為不同的類,儘管他們擁有相同的全限定名稱。嘗試強轉一個object的類別為另一個類別的參考將拋出ClassCastException。
有可能繞過這個限制,但是你必須用兩種方式來改變你的程式碼:

  1. 使用介面作為變數類型,並且只重新載入實作類別

  2. 使用超類別作為變數類型,並且只重新載入子類別

#這裡是範例程式碼:

MyObjectInterface object = (MyObjectInterface)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");
MyObjectSuperclass object = (MyObjectSuperclass)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

如果變數類型是介面或超類,上面的程式碼都會正常運行,介面或超類在重新載入實作或子類時不會被重新載入。
為了上面程式碼的正常運行,你當然需要實作自己的類別載入器,讓介面或超類別由其父類別載入。當你的類別載入器被要求載入MyObject時,它也會被要求載入MyObjectInterface介面或MyObjectSuperclass類,因為它們被MyObject類別在內部引用。你的類別載入器必須把類別載入委派給相同的類別載入器,也就是載入了介面或超類別的類別載入器。

类加载器加载/重新加载示例

上文包含了很多内容。让我们看一下简单的示例。下面是一个简单的ClassLoader子类。注意它如何将类加载委托给它的父类,除了它想要重装的一个类之外。如果类加载被委派给了它的父类,它以后将不能被重新加载。记住,一个类只能被同一个ClassLoader实例加载。
如前所述,这只是一个示例,它显示了类加载器的行为的基本知识。这并不是一个你的类加载器的生产就绪的模板。你的类加载器可能并不仅限于一个类,可能是一个你想要重新加载的类的集合。此外,你也不能硬编码class path。

public class MyClassLoader extends ClassLoader{

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    public Class loadClass(String name) throws ClassNotFoundException {
        if(!"reflection.MyObject".equals(name))
                return super.loadClass(name);

        try {
            String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
                            "classes/reflection/MyObject.class";
            URL myUrl = new URL(url);
            URLConnection connection = myUrl.openConnection();
            InputStream input = connection.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int data = input.read();

            while(data != -1){
                buffer.write(data);
                data = input.read();
            }

            input.close();

            byte[] classData = buffer.toByteArray();

            return defineClass("reflection.MyObject",
                    classData, 0, classData.length);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

}

下面是使用MyClassLoader的示例:

public static void main(String[] args) throws
    ClassNotFoundException,
    IllegalAccessException,
    InstantiationException {

    ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
    MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
    Class myObjectClass = classLoader.loadClass("reflection.MyObject");

    AnInterface2       object1 =
            (AnInterface2) myObjectClass.newInstance();

    MyObjectSuperClass object2 =
            (MyObjectSuperClass) myObjectClass.newInstance();

    //create new class loader so classes can be reloaded.
    classLoader = new MyClassLoader(parentClassLoader);
    myObjectClass = classLoader.loadClass("reflection.MyObject");

    object1 = (AnInterface2)       myObjectClass.newInstance();
    object2 = (MyObjectSuperClass) myObjectClass.newInstance();

}

reflection.MyObject类是由自定义类加载器加载的。注意,它是如何继承一个超类、实现一个接口的。这只是为了这个例子。在你的代码中,只需要两个中的一个,继承超类或实现接口。

public class MyObject extends MyObjectSuperClass implements AnInterface2{
    //... body of class ... override superclass methods
    //    or implement interface methods
}

以上是Java動態類別載入和重新載入的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除