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

发布于 2024-09-13 07:35:16 字数 1330 浏览 11 评论 0原文

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

比方说,我需要类 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 引起的 InitationTargetException。有趣的是,我无法捕获InitationTargetException:无论如何它都会被抛出。

编辑2:
发现原因:我将 SwingUtilities.invokeAndWait(...) 包裹在测试方法周围,并且在加载 Nimbus 时,invokeAndWait 调用抛出 NoClassDefFoundError失败。

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

How do I call a method of a class dynamically + conditionally?
(Class is eventually not in classpath)

Let's say, I need the class NimbusLookAndFeel, but on some systems it's not available (i.e. OpenJDK-6).

So I must be able to:

  • Get to know it that class is available (at runtime),
  • If it's not the case, skip the whole thing.
  • How do I manage to override a method of a dynamically-loaded class
    (thus creating an anonymous inner sub-class of it)?

Code example

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

  });
}

EDIT:
Now I edited my code, as it was suggested, to intercept NoClassDefFoundError using try-catch. It fails. I don't know, if it's OpenJDK's fault. I get InvocationTargetException, caused by NoClassDefFoundError. Funny, that I can't catch InvocationTargetException: It's thrown anyway.

EDIT2::
Cause found: I was wrapping SwingUtilities.invokeAndWait(...) around the tested method, and that very invokeAndWait call throws NoClassDefFoundError when loading Nimbus fails.

EDIT3::
Can anyone please clarify where NoClassDefFoundError can occur at all? Because it seems that it's always the calling method, not the actual method which uses the non-existing class.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

风尘浪孓 2024-09-20 07:35:16

了解类是否可用(在运行时)
将用法放在 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);
   }
}

Get to know it that class is available (at runtime)
Put the usage in a try block ...

If it's not the case, skip the whole thing
... and leave the catch block empty (code smell?!).

How do I manage to override a method of a dynamically-loaded class
Just do it and make sure the compile-time dependency is satisfied. You are mixing things up here. Overriding takes place at compile time while class loading is a runtime thing.

For completeness, every class you write is dynamically loaded by the runtime environment when it is required.

So your code may look like:

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);
   }
}
围归者 2024-09-20 07:35:16

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

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

Use BCEL to generate your dynamic subclass on the fly.

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

毅然前行 2024-09-20 07:35:16

以下代码应该可以解决您的问题。 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.A或者packageB.B。如果有的话,类加载器在尝试加载其中一个时,会在没有 packageA.A 的平台上抛出异常。

它是如何工作的?
在第一个 Class.forName() 中,它检查类 A 在平台上是否可用。如果是,则要求类加载器加载类B,并将生成的Class对象存储在classB中。否则,Class.forName() 会引发 ClassNotFoundException,并且程序将没有类 A

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

然后,如果 constructorB 不为 null,则调用 constructorB.newInstance() 创建一个 B 对象。由于有一个 C 对象作为参数,因此您可以创建一个实现接口 C 的匿名类,并将实例作为参数值传递。这就像您创建匿名 MouseListener 时所做的那样。

(事实上​​,你不必将上面的 try 块分开。这样做是为了清楚我在做什么。)

如果你让 B 实现 < code>C,此时可以将B对象强制转换为C引用,然后就可以直接调用重写的方法(无需反射)。

如果类 A 没有“无参数构造函数”怎么办?
只需将所需的参数添加到类 B 中,例如 public B(int extraParam, C c),并调用 super(extraParam) 而不是 <代码>超级()。创建constructorB时,还要添加额外的参数,例如classB.getConstructor(Integer.TYPE, C.class)

字符串s和字符串t会发生什么?
t 由匿名类直接使用。当调用 objectB.doSomething("World"); 时,"World" 是提供给类 Bs >。由于 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 外观和感觉的正确方法的链接:

Nimbus 外观

The follow code should solve your problem. The Main class simulates your main class. Class A simulates the base class you want to extend (and you have no control of). Class B is the derived class of class A. Interface C simulates "function pointer" functionality that Java does not have. Let's see the code first...

The following is class A, the class you want to extend, but have no control of:


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

The following is class B, the dummy derived class. Notice that, since it extends A, it must import packageA.A and class A must be available at the compile time of class B. A constructor with parameter C is essential, but implementing interface C is optional. If B implements C, you gain the convenience to call the method(s) on an instance of B directly (without reflection). In B.doSomething(), calling super.doSomething() is optional and depends on whether you want so, but calling c.doSomething() is essential (explained below):


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

The following is the tricky interface C. Just put all the methods you want to override into this interface:


/* src/packageC/C.java */

package packageC;

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

The following is the main class:


/* 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");
        }
    }
}

Why does it compile and run?
You can see that in the Main class, only packageC.C is imported, and there is no reference to packageA.A or packageB.B. If there is any, the class loader will throw an exception on platforms that don't have packageA.A when it tries to load one of them.

How does it work?
In the first Class.forName(), it checks whether class A is available on the platform. If it is, ask the class loader to load class B, and store the resulting Class object in classB. Otherwise, ClassNotFoundException is thrown by Class.forName(), and the program goes without class A.

Then, if classB is not null, get the constructor of class B that accepts a single C object as parameter. Store the Constructor object in constructorB.

Then, if constructorB is not null, invoke constructorB.newInstance() to create a B object. Since there is a C object as parameter, you can create an anonymous class that implements interface C and pass the instance as the parameter value. This is just like what you do when you create an anonymous MouseListener.

(In fact, you don't have to separate the above try blocks. It is done so to make it clear what I am doing.)

If you made B implements C, you can cast the B object as a C reference at this time, and then you can call the overridden methods directly (without reflection).

What if class A does not have a "no parameter constructor"?
Just add the required parameters to class B, like public B(int extraParam, C c), and call super(extraParam) instead of super(). When creating the constructorB, also add the extra parameter, like classB.getConstructor(Integer.TYPE, C.class).

What happens to String s and String t?
t is used by the anonymous class directly. When objectB.doSomething("World"); is called, "World" is the s supplied to class B. Since super can't be used in the anonymous class (for obvious reasons), all the code that use super are placed in class B.

What if I want to refer to super multiple times?
Just write a template in B.doSomething() like this:


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

Of course, you have to modify interface C to include doSomethingAfter1() and doSomethingAfter2().

How to compile and run the code?

$ 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

In the first run, the class packageB.B is not compiled (since Main.java does not have any reference to it). In the second run, the class is explicitly compiled, and thus you get the result you expected.

To help you fitting my solution to your problem, here is a link to the correct way to set the Nimbus Look and Feel:

Nimbus Look and Feel

诗化ㄋ丶相逢 2024-09-20 07:35:16

您可以使用 Class 类来这样做。

IE:

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

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

You can use Class class to do that.

I.E.:

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

The sentence above will throw a ClassNotFoundException if not found on current classpath. If the exception is not thrown, then you can use newInstance() method in c to create objects of your.package.YourClass class. If you need to call a specific constructor, you can use getConstructors method to get one and use it to create a new instance.

旧瑾黎汐 2024-09-20 07:35:16

呃,难道你不能将你想要扩展的类放入编译时类路径中,像往常一样编写你的子类,并在运行时显式触发加载子类,并处理链接器抛出的任何指示超类是的异常吗?丢失的?

Erm, can't you put the class you want to extend into the compile time class path, write your subclass as usual, and at runtime, explicitly trigger loading the subclass, and handle any exception thrown by the linker that indicates that the superclass is missing?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文