是否有可能在Java中修补补丁?

时间:2008-12-19 15:10:24

标签: java reflection monkeypatching

我不想讨论这种方法的优点,只要有可能。我相信答案是“不”。但也许有人会让我感到惊讶!

想象一下,你有一个核心小部件类。它有一个方法calculateHeight(),返回一个高度。高度太大 - 这导致按钮(比如说)太大了。您可以扩展DefaultWidget以创建自己的NiceWidget,并实现自己的calculateHeight()以返回更好的大小。

现在,一个库类WindowDisplayFactory,在一个相当复杂的方法中实例化DefaultWidget。您希望它使用您的NiceWidget。工厂类的方法看起来像这样:

public IWidget createView(Component parent) {
    DefaultWidget widget = new DefaultWidget(CONSTS.BLUE, CONSTS.SIZE_STUPIDLY);

    // bunch of ifs ...
    SomeOtherWidget bla = new SomeOtherWidget(widget);
    SomeResultWidget result = new SomeResultWidget(parent);
    SomeListener listener = new SomeListener(parent, widget, flags);

    // more widget creation and voodoo here

    return result;
}

这就是交易。结果使DefaultWidget深入其他对象的层次结构中。问题 - 如何让这个工厂方法使用我自己的NiceWidget?或者至少在那里获得我自己的calculateHeight()。理想情况下,我希望能够修补DefaultWidget,以便它的calculateHeight做正确的事......

public class MyWindowDisplayFactory {
    public IWidget createView(Component parent) {
        DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight);
        return super.createView(parent);
    }
}

我可以在Python,Ruby等中做些什么。我发明了名字setMethod()。我可以选择的其他选择是:

  • createView()方法的代码复制并粘贴到我自己的继承工厂类的类中
  • 与太大的小部件一起生活

工厂类无法更改 - 它是核心平台API的一部分。我尝试对返回的结果进行反射以获得(最终)添加的小部件,但它是几个小部件层向下,并且它用于初始化其他东西,导致奇怪的副作用。

有什么想法吗?到目前为止,我的解决方案是复制粘贴工作,但这是一个需要在升级到更新版本的平台时跟踪父工厂类中的更改的警察,我有兴趣听到其他选项。

9 个答案:

答案 0 :(得分:9)

也许您可以使用面向方面编程来捕获对该函数的调用并返回您自己的版本?

Spring提供了一些AOP功能,但也有其他库也可以这样做。

答案 1 :(得分:7)

一个丑陋的解决方案是在类路径上比普通实现更早地放置自己的DefaultWidget实现(使用相同的FQCN)。这是一个可怕的黑客,但我能想到的其他方法更糟糕。

答案 2 :(得分:3)

只是我的概念,

有可能使用字节码工程方式的AOP向calculateHeight方法注入一个方面。

然后,您可以通过ThreadLocal或其他变量启用补丁。

答案 3 :(得分:2)

cglib是一个Java库,它可以做类似于猴子修补的东西 - 它可以在运行时操作字节码来改变某些行为。我不确定它是否能完全满足您的需求,但值得一看......

答案 4 :(得分:2)

使用Unsafe.putObject和类查找器完全可以在Java中使用monkeypatch。在这里写了一篇博文:

https://tersesystems.com/blog/2014/03/02/monkeypatching-java-classes/

答案 5 :(得分:0)

面向对象的方法是创建一个实现IWidget的包装器,将所有调用委托给实际的小部件,除了calculateHeight,类似于:

class MyWidget implements IWidget {
    private IWidget delegate;
    public MyWidget(IWidget d) {
        this.delegate = d;
    }
    public int calculateHeight() {
        // my implementation of calculate height
    }
    // for all other methods: {
    public Object foo(Object bar) {
        return delegate.foo(bar);
    }
}

为此,您需要拦截要替换的窗口小部件的所有创建,这可能意味着为WidgetFactory创建类似的包装器。并且您必须能够配置要使用的WidgetFactory。

它还取决于没有客户端尝试将IWidget强制转换回DefaultWidget ...

答案 6 :(得分:0)

只有我能想到的建议:

  1. 深入了解库API,看看是否有某种方法可以覆盖默认值和大小调整。大小调整可能会让人感到困惑(至少对我而言),setMinimum,setMaximum,setdefault,setDefaultOnThursday,....有可能有办法。如果您可以联系图书馆设计师,您可能会找到一个答案,可以减轻对令人不快的黑客攻击的需求。

  2. 或许扩展工厂只会覆盖一些默认的大小调整参数?取决于工厂,但它可能是可能的。

  3. 创建一个具有相同名称的类可能是唯一的另一个选项,因为其他人已经指出它很难看,当你更新api库或在不同的环境中部署并忘记原因时,你可能会忘记它并破坏它们你有这样的类路径设置。

答案 7 :(得分:0)

您可以尝试使用PowerMock / Mockito等工具。如果你可以在测试中模拟,你也可以在生产中进行模拟。

然而,这些工具实际上并没有被设计为以这种方式使用,因此您必须自己准备环境,并且无法像在测试中那样使用JUnit运行器...

答案 8 :(得分:-1)

好吧,我一直试图发布建议,然后我发现它们不起作用,或者你已经提到过你尝试过它们。

我能想到的最好的解决方案是继承WindowDisplayFactory,然后在子类的createView()方法中,首先调用super.createView(),然后修改返回的对象以完全抛出小部件并将其替换为实例做你想要的子类。但是小部件用于初始化东西,所以你必须改变所有这些。

然后我想到使用createView()返回对象的反射并尝试以这种方式解决问题,但是再次,这很毛茸茸,因为很多东西都是用小部件初始化的。不过,我认为我会尝试使用这种方法,如果它足够简单,可以证明它在复制和粘贴方面的合理性。

我会看着这个,还想着看看我是否能提出任何其他想法。 Java Reflection肯定很好,但它无法击败我在Perl和Python等语言中看到的动态内省。