如何在运行时合并多个Java类*?

时间:2011-11-18 20:57:30

标签: java

假设您有一个应用程序,它定义了一个名为A的“实体”(接口),并带有一个AImpl。你有第三方插件,它将A类扩展为B,C,.. Z(带有BImpl,...),每个插件都添加了一些状态和功能。您的应用程序中还有一个“工厂”,用于创建A的实例。

我希望我的“用户”同时加载几个插件,比如F,K和W,这样工厂就会创建一个同时为F,K的“超级A”实例假设“接口”和“实现”是严格分开的,只要“超级A”实现F,K和W的接口,它就不应该是它既不是FImpl,也不是KImpl或WImpl。所以是一种“运行时”多重继承。

准确地说,我没有F,K或W的源代码,第三方开发人员不需要知道其他人的扩展。

那么,是否有一些库可以轻松合并Java类?理想情况下,使用某种方式管理“冲突”,例如两个类定义具有相同签名的私有方法,或尝试覆盖相同的基类方法。

注意:我正在寻找使用字节码操作而不是组合的东西。组合可以防止基类的重写方法的“扩展”类,并使用更多的内存。

[编辑]正如我在对@Gray的评论中所解释的那样,出于多种原因,构图不是一个好的解决方案。虽然你可以使用它,但如果你正确地使用它,你最终会得到很多非常小的类和接口,以及大量的粘合代码,使它们看起来就像一个单独的对象。除了大量的编码开销导致生产力下降之外,由于各处都增加了间接性,因此应用程序使用更多内存并且速度也慢得多。

对于处理小型和短型数据库事务的应用程序,组合可能是完美的,但对于必须始终在RAM中保持千兆字节热数据的应用程序,这就是我将要做的,这是非常真实的成本,包括开发时间,客户端和服务器的内存和CPU要求。

一种解决方案,你可以“自然地”编码(接口优先的编程风格是我通常编码的方式),对你可以扩展的基类中的哪些方法有一些限制,会比组合“更便宜”

3 个答案:

答案 0 :(得分:2)

虽然它不是一个易于使用的库,但到目前为止我发现的最接近的是在ASM中执行它的“配方”:

Merging class methods with ASM

但正如页面所说,它不能处理极端情况,这就是为什么拥有一个可以更容易实现它的库。

答案 1 :(得分:1)

您可以使用注册了所有F,K和W插件的代理对象来执行此操作。如果它们是您的问题所暗示的接口,则更容易做到。如果没有,那么你将使用cglib做更多的魔术。代理文档在这里:

  

http://download.oracle.com/javase/1.5.0/docs/guide/reflection/proxy.html

以下是根据示例调整的代码示例:

public class MyProxy implements InvocationHandler {
    private F f; private K k; private W w;

    public static Object newInstance(F f, K k, W w) {
        return java.lang.reflect.Proxy.newProxyInstance(
            obj.getClass().getClassLoader(),
            new Class[] { F.class, K.class, W.class ],
            new MyProxy(f, k, w));
    }

    private MyProxy(F f, K k, W w) {
        this.f = f; this.k = k; this.w = w;
    }

    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable{
        try {
            // pseudo code starts here
            if (f has method that matches m.getName() and args) {
                find method from f
                return fMethod.invoke(obj, args);
            } else (same thing for f and w ... ) {}
            throw some exception about missing method
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (Exception e) {
            throw new RuntimeException("unexpected invocation exception", e);
        }
    }
}

如果你想在某个插件中调用一个方法,你可以创建一个方法地图和每个调用数据。 @DaveNewton是对的,如果其中两个具有相同参数的相同方法名称,那就是诀窍。

答案 2 :(得分:0)

这并不像你想象的那样微不足道。但是,是的,这个“基于插件的架构”有框架。这些天最受欢迎的一个是:http://www.osgi.org/Main/HomePage