【问题标题】:In Java, is there a way load class implementing interface that does not exist?在Java中,有没有办法加载不存在的实现接口的类?
【发布时间】:2011-09-12 08:45:20
【问题描述】:

我已经看到 Java 做了很多魔术,但有可能做到这一点:

在运行时,使用(例如)ClassLoader.defineClass,加载实现接口B 的类A。接口B 实际上并不存在于类路径中。 Java 将抛出异常(ClassNotFoundExceptionIIRC)并且不会加载该类。 A 类的所有其他部分都可以,而且我知道程序的其他部分不会使用接口B。所以,我想做的是让解释器忽略缺少的接口定义,并加载一个与A完全相同的类,除了它没有实现接口B

这可能吗?当然可以通过捕获异常并手动编辑类A的二进制数据,然后再次加载来实现。或者通过手动构造 B.class 文件然后加载它,在运行时创建一个名为 B 的虚拟空接口。但这似乎有点乱,所以我的问题是,Java 是否提供了任何方便的方法来做到这一点?

如果没有,我想,我会尝试实现这两种方法中的一种,但我仍然想听听意见。

我这样做是为了为两个不同的代码库提供一种方便的方式,以便在它们都被加载的情况下相互交互,并且如果只有一个代码库能够正常工作。

【问题讨论】:

  • 你正在尝试做一个混乱的工作,所以所有的解决方案都会很混乱。我认为这种特殊方法没有办法解决“混乱”。
  • 那些必须是邪恶的代码库,如果你必须在那个级别进行调整以使它们交互/独立工作......
  • ...先加载界面怎么样?只是一个想法......

标签: java interface classloader .class-file


【解决方案1】:

我不知道有任何与您所描述的类似的非混乱解决方案。这一切都归结为自定义ClassLoader。自定义ClassLoaders 有点难以开始,如果它们具有像这样非常具体的语义,那么它们就会变得非常丑陋。

更不用说如果您的代码突然ClassLoader 中运行会遇到的问题类型。

我认为理智的解决方案是生成mylibrary.jarmylibrary-noB.jar(或者更明确:mylibrary-withB.jarmylibrary-noB.jar)并让用户选择哪个他们想要的。

【讨论】:

  • 我正在为电脑游戏制作插件系统。想象一下,三个独立的插件通过接口 B、C 和 D 提供了一些可选功能。插件 A 添加了许多可能通过这些接口与插件 B、C 和 D 交互的东西。按照您的方式,A 的作者将不得不提供其插件的 8 个版本。我的方式是,他只需要编写他的代码并从中生成 .jar。当然,如果采用不同的方式,也有允许交互的方式,但我认为这对于那些将要编写这些插件的人来说是最方便的。
  • @AUTOMATIC:您的解决方案的问题在于它仅涵盖运行时不存在的库中的 实现 接口。但是,如果该类实际上想要处理该类型的对象怎么办?如果插件 A 想要选择性地与插件 B 交互,那么 可能 不足以实现一些 Bs 接口,您可能还需要调用它。在这种情况下,基于插件的架构的常见解决方案是提供一个 A.B 子插件,它添加了 A 的基于 B 的功能并依赖于 A 和 B。如果没有给出 B,则只有 A 基础插件已加载。
  • 另外:即使开发人员只需要 1 个 jar 而不是 8 个,他也必须测试 8 个不同的场景,因为即使只生成一个 jar :它在任何这些场景中表现不同。因此,只需要生产 1 个罐子并不能真正让事情变得容易得多。
  • 在某些情况下,您是对的,需要的不仅仅是实现接口。但是当它不是时,我仍然想提供一个更简单的替代方案(从用户的角度来看)。关于测试——没有人会在每次发布新版本时都测试所有 8 种变体,尤其是在进行了不相关的更改之后。
  • 我仍然认为任何字节码操作方法都将非常复杂、难以理解、难以维护并且相当不直观。而且可能的收益相对较小,因为它只涵盖了所有可能用途的一小部分。
【解决方案2】:

在附加接口中包含第二个 jar 是很常见的。如果需要,可以包括第二个罐子,如果不需要,可以丢弃。如果将其添加到类路径的末尾,这将隐式发生。

【讨论】:

    【解决方案3】:

    最终,定义接口B的类路径条目中的代码必须使用与定义类A的类路径条目中的代码相同的类。所以定义一个虚拟接口是行不通的。

    在 Java 中,您可以使用 Proxy 动态实现接口:

    http://download.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html

    所以在稍后的某个时间点,当你的接口A 可用时,你可以通过反射获取它并创建实现其方法的代理。下面的例子显示了java.lang.Runnable 接口作为接口A

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class ProxyTest {
    
        public static void main(String[] args) throws Exception {
            Class interfaceClass = Class.forName("java.lang.Runnable");
    
            Object implementingRunnable = Proxy.newProxyInstance(
                        ProxyTest.class.getClassLoader(),
                        new Class[] {interfaceClass},
                        new MyInvocationHandler()
            );
    
            ((Runnable)implementingRunnable).run();
    
    
        }
    
        static class MyInvocationHandler implements InvocationHandler {
    
            public Object invoke(Object proxy, Method m, Object[] args)
                    throws Throwable {
                System.out.println("called " + m);
                return null;
            }
        }
    }
    

    当然,在某些时候您必须将代理实际转换为接口A。您必须在与接口在同一类路径条目中定义的类中执行此操作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-10
      • 2011-05-31
      • 2015-09-25
      • 1970-01-01
      • 1970-01-01
      • 2022-01-10
      • 2020-12-27
      相关资源
      最近更新 更多