簡體   English   中英

使用類加載器隔離靜態單例類

[英]Isolating a static singleton class using class loaders

免責聲明:鑒於此問題,這可能不是最佳解決方案,但我很好奇如何實現這一實施。

問題我正在嘗試處理一些遺留代碼,這些代碼的單例定義如下:

public class LegacySingleton {
    private static Boolean value;

    public static void setup(boolean v) {
        if (value != null) {
            throw new RuntimeException("Already Set up");
        }
        value = v;
        System.out.println("Setup complete");
    }

    public static void teardown() {
        value = null;
        System.out.println("Teardown complete");
    }

    public static boolean getValue() {
        return value;
    }
}

我無法更改此設計,並且在整個代碼庫中大量使用該類。 此單例返回的值可以極大地改變代碼的功能。 例如:

public class LegacyRequestHandler {
    public void handleRequest() {
        if (LegacySingleton.getValue()) {
            System.out.println("Path A");
        } else {
            System.out.println("Path B");
        }
    }
}

現在,如果我希望代碼采用Path A ,那么我必須以特定方式初始化LegacySingleton 如果我想要使用Path B我必須重新初始化LegacySingleton 無法並行處理采用不同路徑的請求; 對於LegacySingleton每個不同配置的LegacySingleton ,我需要啟動一個單獨的JVM實例。


我的問題是否有可能使用單獨的類加載器來隔離這個單例? 我一直在使用ClassLoader API,但我無法弄明白。

我想它會看起來像這樣:

public class LegacyRequestHandlerProvider extends Supplier<LegacyRequestHandler> {
    private final boolean value;
    public LegacyRequestHandlerProvider(boolean value) {
        this.value = value;
    }
    @Override
    public LegacyRequestHandler get() {
        LegacySingleton.setup(value);
        return new LegacyRequestHandler();
    }
}

...

ClassLoader loader1 = new SomeFunkyClassLoaderMagic();
Supplier<LegacyRequestHandler> supplier1 = loader1
    .loadClass("com.project.LegacyRequestHandlerProvider")
    .getConstructor(Boolean.TYPE)
    .newInstance(true);

ClassLoader loader2 = new SomeFunkyClassLoaderMagic();
Supplier<LegacyRequestHandler> supplier2 = loader2
    .loadClass("com.project.LegacyRequestHandlerProvider")
    .getConstructor(Boolean.TYPE)
    .newInstance(false);

LegacyRequestHandler handler1 = supplier1.get();
LegacyRequestHandler handler2 = supplier2.get();

/ 首先,告訴你的主管你是在花時間制作使用更多內存的復雜代碼,並且由於jit重新編譯和其他類init問題可能會變慢,因為你不知道如何修復糟糕的過時代碼。 時間和可維護性就是金錢。 /

好的,現在......你的時髦的類加載器只是帶有所需罐子的URL類加載器。 但訣竅是在主類路徑中沒有單例和處理程序,否則類加載器將在parentclassloader(具有優先權)中找到該類,並且它仍然是單例。 你知道我很有信心。

另一個根本的解決方案是以你的方式重新實現違規類(使用屬性文件,或者使用Threadlocal字段(假設工作在同一個線程上完成),調用者可以在調用處理程序之前設置它,這反過來將看不到mascarade)。

您必須在類路徑中優先部署覆蓋類(在前面列出jar),或者如果可以的話,在web-inf / classes中部署web-inf / lib中的任何內容。 您最終可以從舊jar中刪除該類。 重點是具有相同的類名,相同的方法簽名,但是一個新的實現(同樣,它依賴於加載cfg文件或在調用之前使用threadlocal設置)。

希望這可以幫助。

簡單的簡單反射是否適用於特定時間點,您希望getValue的輸出更改?

Field f = LegacySingleton.class.getDeclaredField("value");
f.setAccessible(true);
f.set(null, true|false);

如果沒有,對於Classloader方法,您可以遵循插件架構。 但正如其他人所說,這可能歸結為在2個不同的Classloader層次結構上加載的整個依賴關系。 此外,您可能會遇到LinkageError問題,具體取決於依賴項在代碼庫中的工作方式。

靈感來自這篇文章

  • 隔離代碼庫,主要是具有LegacyRequestHandler類的jar,並且不包含在application / main類路徑中。
  • 有一個自定義類加載器,首先向內看,然后檢查父類。 這樣的類加載器的一個完整的例子是在這里
  • 有一個包裝調用程序,它將使用提供LegacySingleton類的jar路徑初始化類加載器,例如

    new ParentLastURLClassLoader(Arrays.asList(new URL[] {new URL("path/to/jar")}));

  • 發布,您可以在其類加載器空間中加載單例並獲取副本。


    //2 different classloaders 
    ClassLoader cl1 = new ParentLastURLClassLoader(urls);
    ClassLoader cl2 = new ParentLastURLClassLoader(urls);
    //LegacySingleton with value = true in Classloader space of cl1
    cl1.loadClass("LegacySingleton").getMethod("setup", boolean.class).invoke(null, true);
    //LegacySingleton with value = false in Classloader space of cl1
    cl2.loadClass("LegacySingleton").getMethod("setup", boolean.class).invoke(null, false);
  • 接下來,您可以使用反射(通過cl1/2 )和觸發器執行來獲取遺留代碼的驅動程序類。

請注意 ,您不應直接在主類中引用Legacy代碼中的類,因為它們將使用Java原始/應用程序類加載器加載。

在我看來 - 我很抱歉這是一個基於意見的答案 - 這是一個業務問題而不是技術問題,因為給出的限制(“我不能改變代碼”)不是技術問題。 但正如任何從事軟件開發工作的人都可以證明的那樣,業務限制是我們工作的重要組成部分。

您的問題可以抽象如下:“考慮約束A,我可以得到結果B嗎?” 答案是:“不,你不能。” 或者也許你可以,但是用一個難以解決的解決方案 - 換句話說,昂貴 - 維護並且容易中斷。

在這些情況下,最好知道為什么你不能改變那些有明顯和非常嚴重的設計問題的軟件。 因為那是真正的問題。

暫無
暫無

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

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