簡體   English   中英

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

[英]Is it possible to monkey patch in 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()方法的代碼復制並粘貼到我自己的繼承工廠類的類中
  • 生活在太大的小部件

工廠類無法更改 - 它是核心平台API的一部分。 我嘗試對返回的結果進行反射以獲得(最終)添加的小部件,但它是幾個小部件層向下並且在某處它用於初始化其他東西,導致奇怪的副作用。

有任何想法嗎? 到目前為止,我的解決方案是復制粘貼工作,但這是一個警察需要在升級到更新版本的平台時跟蹤父工廠類的更改,我有興趣聽到其他選項。

也許您可以使用面向方面編程來捕獲對該函數的調用並返回您自己的版本?

Spring提供了一些AOP功能,但也有其他庫也可以。

一個丑陋的解決方案是在類路徑上比普通實現更早地放置自己的DefaultWidget實現(使用相同的FQCN)。 這是一個可怕的黑客,但我能想到的其他方法更糟糕。

只是我的概念,

可以使用字節碼工程方式的AOP向calculateHeight方法注入一個方面。

然后,您可以通過ThreadLocal或其他變量啟用補丁。

cglib是一個Java庫,它可以做一些類似於猴子修補的東西 - 它可以在運行時操作字節碼來改變某些行為。 我不確定它是否可以完全滿足您的需求,但值得一看......

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

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

您可以嘗試使用PowerMock / Mockito等工具。 如果你可以在測試中模擬,你也可以在生產中進行模擬。

但是這些工具並沒有真正設計為以這種方式使用,因此您必須自己准備環境,並且無法像在測試中那樣使用JUnit運行器...

這種面向對象的方法是創建一個實現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以這種方式設置。

好吧,我一直試圖發布建議,然后我發現它們不起作用,或者你已經提到過你試過它們。

我能想到的最好的解決方案是繼承WindowDisplayFactory,然后在子類的createView()方法中,首先調用super.createView(),然后修改返回的對象以完全拋出小部件並將其替換為子類的實例做你想要的。 但是小部件用於初始化東西,所以你必須改變所有這些。

然后我想到使用createView()返回對象的反射並嘗試以這種方式解決問題,但是再次,這很毛茸茸,因為很多東西都是用小部件初始化的。 不過,我認為我會嘗試使用這種方法,如果它足夠簡單,可以證明它在復制和粘貼方面的合理性。

我會看着這個,還想着看看我是否能提出任何其他想法。 Java Reflection肯定很好,但它無法超越我在Perl和Python等語言中看到的動態內省。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM