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

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

想象一下,你有一个核心小部件类。 它有一个方法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()方法的代码复制并粘贴到我自己的inheritance工厂类的类中
  • 生活在太大的小部件

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

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

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

Spring提供了一些AOPfunction,但也有其他库也可以。

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

只是我的概念,

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

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

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

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

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

这种面向对象的方法是创建一个实现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 …

只有我能想到的建议:

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

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

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

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

但是这些工具并没有真正设计为以这种方式使用,因此您必须自己准备环境,并且无法像在测试中那样使用JUnit运行器…

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

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

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

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