簡體   English   中英

Java:如何動態重寫類的方法(類最終不在類路徑中)?

[英]Java: How do I override a method of a class dynamically (class is eventually NOT in classpath)?

如何動態地+有條件地調用類的方法?
(類最終不在類路徑中)

假設我需要類NimbusLookAndFeel ,但是在某些系統上它不可用(即OpenJDK-6 )。

因此,我必須能夠:

  • 了解類是否可用 (在運行時),
  • 如果不是這種情況,請跳過整個過程。
  • 我如何設法覆蓋動態加載的類的方法
    (從而創建它的匿名內部子類)?

代碼示例

public static void setNimbusUI(final IMethod<UIDefaults> method)
    throws UnsupportedLookAndFeelException {

  // NimbusLookAndFeel may be now available
  UIManager.setLookAndFeel(new NimbusLookAndFeel() {

    @Override
    public UIDefaults getDefaults() {
      UIDefaults ret = super.getDefaults();
      method.perform(ret);
      return ret;
    }

  });
}

編輯
現在,根據建議,我編輯了代碼,以使用try-catch攔截NoClassDefFoundError 它失敗。 我不知道這是否是OpenJDK的錯。 我收到由NoClassDefFoundError引起的InvocationTargetException 有趣的是,我無法捕獲InvocationTargetException :無論如何,它被拋出。

EDIT2 ::
找到原因:我將SwingUtilities.invokeAndWait(...)包裝在經過測試的方法周圍,並且在加載Nimbus失敗時,非常invokeAndWait調用引發NoClassDefFoundError

EDIT3 ::
任何人都可以澄清在哪里可能發生NoClassDefFoundError嗎? 因為似乎總是調用方法,而不是使用不存在的類的實際方法。

了解類是否可用(在運行時)
將用法放在try塊中...

如果不是這種情況,請跳過整個過程
...,然后將捕獲塊留空(代碼氣味?!)。

我如何設法覆蓋動態加載的類的方法
只要做到這一點,並確保滿足編譯時依賴性。 您正在把事情混在一起。 覆蓋發生在編譯時,而類加載是運行時的事情。

為了完整性,需要時,運行時環境會動態加載您編寫的每個類。

因此您的代碼可能如下所示:

public static void setNimbusUI(final IMethod<UIDefaults> method)
    throws UnsupportedLookAndFeelException {

    try {
        // NimbusLookAndFeel may be now available
        UIManager.setLookAndFeel(new NimbusLookAndFeel() {

            @Override
            public UIDefaults getDefaults() {
                final UIDefaults defaults = super.getDefaults();
                method.perform(defaults);
                return defaults;
            }

        });
   } catch (NoClassDefFoundError e) {
       throw new UnsupportedLookAndFeelException(e);
   }
}

使用BCEL即時生成動態子類。

http://jakarta.apache.org/bcel/manual.html

以下代碼應該可以解決您的問題。 Main類模擬您的主類。 A模擬您要擴展的基類(您無法控制)。 B類是A類的派生類。 接口C模擬Java沒有的“函數指針”功能。 我們先來看代碼...

以下是類A ,您要擴展但無法控制的類:


/* src/packageA/A.java */

package packageA;

public class A {
    public A() {
    }

    public void doSomething(String s) {
        System.out.println("This is from packageA.A: " + s);
    }
}

以下是類B (偽派生類)。 請注意,由於它擴展了A ,因此必須導入packageA.A並且在類B的編譯時類A必須可用。 具有參數C的構造函數是必不可少的,但實現接口C是可選的。 如果B實現C ,則可以方便地直接在B的實例上調用方法(無反射)。 B.doSomething() ,調用super.doSomething()是可選的,並取決於您是否c.doSomething() ,但是調用c.doSomething()是必不可少的(如下所述):


/* src/packageB/B.java */

package packageB;

import packageA.A;
import packageC.C;

public class B extends A implements C {
    private C c;

    public B(C c) {
        super();
        this.c = c;
    }

    @Override
    public void doSomething(String s) {
        super.doSomething(s);
        c.doSomething(s);
    }
}

以下是棘手的接口C 只需將要覆蓋的所有方法放入此接口:


/* src/packageC/C.java */

package packageC;

public interface C {
    public void doSomething(String s);
}

以下是主要類:


/* src/Main.java */

import packageC.C;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Main {
    public static void main(String[] args) {
        doSomethingWithB("Hello");
    }

    public static void doSomethingWithB(final String t) {
        Class classB = null;
        try {
            Class classA = Class.forName("packageA.A");
            classB = Class.forName("packageB.B");
        } catch (ClassNotFoundException e) {
            System.out.println("packageA.A not found. Go without it!");
        }

        Constructor constructorB = null;
        if (classB != null) {
            try {
                constructorB = classB.getConstructor(C.class);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }

        C objectB = null;
        if (constructorB != null) {
            try {
                objectB = (C) constructorB.newInstance(new C() {
                    public void doSomething(String s) {
                        System.out.println("This is from anonymous inner class: " + t);
                    }
                });
            } catch (ClassCastException e) {
                throw new RuntimeException(e);
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }

        if (objectB != null) {
            objectB.doSomething("World");
        }
    }
}

為什么要編譯並運行?
您可以看到在Main類中,僅導入packageC.C ,並且沒有對packageA.ApackageB.B引用。 如果有的話,類加載器將在嘗試不加載packageA.A平台上拋出異常。

它是如何工作的?
在第一個Class.forName() ,它檢查類A在平台上是否可用。 如果是,請讓類加載器加載類B ,並將生成的Class對象存儲在classB 否則, Class.forName()會引發ClassNotFoundException ,並且程序將不包含A類。

然后,如果classB不為null,則獲取接受單個C對象作為參數的B類的構造函數。 存儲Constructor的對象constructorB

然后,如果constructorB constructorB.newInstance() B不為null,則調用constructorB.newInstance() B.newInstance constructorB.newInstance()創建一個B對象。 由於有一個C對象作為參數,因此您可以創建一個實現接口C的匿名類,並將實例作為參數值傳遞。 就像創建匿名MouseListener時所做的一樣。

(實際上,您不必分開上面的try塊。這樣做是為了明確我在做什么。)

如果使B實現C ,則可以在此時將B對象轉換為C引用,然后可以直接調用重寫的方法(無反射)。

如果類A沒有“無參數構造函數”怎么辦?
只需將所需的參數添加到類B ,例如public B(int extraParam, C c) ,然后調用super(extraParam)而不是super() 創建constructorB ,還添加額外的參數,例如classB.getConstructor(Integer.TYPE, C.class)

String s和String t怎樣?
t由匿名類直接使用。 objectB.doSomething("World"); 被稱為, "World"s供應給類B 由於super不能在匿名類(原因很明顯)被使用,所有使用該代碼super放在類B

如果我想多次提及super怎么辦?
只需在B.doSomething()編寫一個模板,如下所示:


    @Override
    public void doSomething(String s) {
        super.doSomething1(s);
        c.doSomethingAfter1(s);
        super.doSomething2(s);
        c.doSomethingAfter2(s);
    }

當然,您必須修改接口C以包括doSomethingAfter1()doSomethingAfter2()

如何編譯和運行代碼?

$ mkdir classes
$
$
$
$ javac -cp src -d classes src/Main.java
$ java -cp classes Main
packageA.A not found. Go without it!
$
$
$
$ javac -cp src -d classes src/packageB/B.java
$ java -cp classes Main
This is from packageA.A: World
This is from anonymous inner class: Hello

在第一次運行中,不編譯packageB.B類(因為Main.java沒有對其的任何引用)。 在第二次運行中,該類被顯式編譯,因此您將獲得預期的結果。

為了幫助您使我的解決方案適應您的問題,以下是設置Nimbus外觀的正確方法的鏈接:

靈氣外觀

您可以使用Class類來做到這一點。

IE瀏覽器:

Class c = Class.forName("your.package.YourClass");

如果在當前classpath上找不到,則以上語句將引發ClassNotFoundException。 如果未引發異常,則可以在c使用newInstance()方法創建.package.YourClass類的對象。 如果需要調用特定的構造函數,則可以使用getConstructors方法獲取一個並使用它創建一個新實例。

恩,您不能將要擴展的類放入編譯時類路徑中,照常編寫子類,然后在運行時顯式觸發加載子類,並處理鏈接器拋出的任何異常,這些異常表明超類是失蹤?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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