Java:如何动态覆盖类的方法(类最终不在类路径中)?

时间:2010-08-07 19:32:38

标签: java reflection classpath openjdk nimbus

如何动态+有条件地调用类的方法?
(Class在类路径中最终

假设我需要课程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的错。我得到InvocationTargetException,由NoClassDefFoundError引起。有趣的是,我无法抓住InvocationTargetException:无论如何都会抛出它。

EDIT2:
发现原因:我在测试方法周围缠绕SwingUtilities.invokeAndWait(...),并且在加载Nimbus失败时,非常invokeAndWait调用抛出NoClassDefFoundError

EDIT3:
任何人都可以澄清哪里 NoClassDefFoundError可以发生什么?因为它似乎总是调用方法,而不是使用不存在的类的实际方法。

5 个答案:

答案 0 :(得分:4)

了解该类是否可用(在运行时)
将用法放在try块中......

如果不是这样,请跳过整个事情
...并将catch块留空(代码味道?!)。

如何设法覆盖动态加载类的方法
只需这样做并确保满足编译时依赖性。你在这里搞混合了。覆盖发生在编译时,而类加载是运行时的事情。

为了完整性,您编写的每个类都是在需要时由运行时环境动态加载的。

所以你的代码可能如下:

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);
   }
}

答案 1 :(得分:1)

使用BCEL动态生成动态子类。

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

答案 2 :(得分:1)

以下代码可以解决您的问题。 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,并且类A必须在类B的编译时可用。具有参数C的构造函数是必不可少的,但实现接口C是可选的。如果B实现C,您可以直接在B的实例上调用方法(无需反射)。在B.doSomething()中,调用super.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中。否则,ClassNotFoundException会抛出Class.forName(),程序将不会上课A

然后,如果classB不为null,则获取接受单个B对象作为参数的类C的构造函数。将Constructor对象存储在constructorB

然后,如果constructorB不为null,则调用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)

字符串s和字符串t会发生什么?
t由匿名类直接使用。调用objectB.doSomething("World");后,"World"是提供给班级s的{​​{1}}。由于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); } 以包含CdoSomethingAfter1()

如何编译和运行代码?

$ 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

在第一次运行中,类doSomethingAfter2()未编译(因为packageB.B没有任何引用)。在第二次运行中,将显式编译该类,从而获得您期望的结果。

为了帮助您解决问题,我们可以通过以下链接找到设置Nimbus外观的正确方法:

Nimbus Look and Feel

答案 3 :(得分:0)

您可以使用Class类来执行此操作。

即:

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

如果在当前类路径中找不到,则上面的句子将抛出ClassNotFoundException。如果未抛出异常,则可以使用newInstance()中的c方法创建 your.package.YourClass 类的对象。如果需要调用特定的构造函数,可以使用getConstructors方法获取一个并使用它来创建新实例。

答案 4 :(得分:0)

嗯,你不能把你想要扩展的类放到编译时类路径中,像往常一样编写子类,并在运行时显式触发加载子类,并处理链接器抛出的任何异常,指示超类缺少?