簡體   English   中英

Java - 在運行時重定位引用

[英]Java - Relocate references at runtime

首先,我知道你可以在不同的構建系統上重新定位編譯jar的所有引用和各種影子插件。 我知道它是如何工作的並且我已經在使用它。 但是我遇到了一個問題,我無法在編譯時這樣做。

我會簡化我的情況所以它更容易理解(但我會解釋底部的全貌,以防你好奇)。
我正在為兩個不同(但相似)的系統(一個jar的forall)編寫一個插件。 這些平台負責啟動底層軟件並加載/啟動所有插件(因此我無法控制應用程序,包括啟動參數)。
平台A為我提供了一個庫(我們稱之為com.example.lib )。 平台B也是如此。 它決定將其重新定位到org.b.shadow.com.example.lib
現在在我的插件的核心代碼(兩個平台上使用的代碼)中我使用了庫。 現在雖然我可以檢測到我在哪個平台上,但我目前不知道如何在運行時將我的代碼中的所有引用重寫到庫中,因此它可以在平台B上運行。

從我發現它似乎我需要使用自定義ClassLoader來實現這一點。 這里的問題是我不知道我可以使運行時使用我的自定義ClassLoader 或者從哪里開始呢。
一個重要的事情是,那些重定位可能只影響我的包中的類的引用(例如me.brainstone.project )。
我使用的另一個依賴項(並且已經使用了陰影)使用了ASM和ASM Commons,因此如果可以使用ASM和ASM Commons,那將是驚人的!

所以總結一下。 我想在運行時只在我的類中重定位引用(到其他類)。

編輯

在我的整個(原始)帖子中,我只談過一個圖書館,我想指出我將為幾個圖書館做這個。 而那些需要我付出巨大努力的事情(為每個庫(類或部分)編寫包裝器將被視為一項重要的工作)允許我使用庫而不是我正在尋找的。 相反,我想要一個解決方案,它需要最少的添加劑來添加新的庫。


現在這里是我的設置的更詳細的解釋。
拳頭我想序言我知道我可以為不同的平台創建兩個不同的罐子。 我已經這樣做了。 但令人驚訝的是,許多人似乎無法弄清楚這一點,我已經厭倦了一遍又一遍地解釋它(那些人不會閱讀文檔以挽救他們的生命)我想提供一個兩個單罐,即使這意味着我需要花費大量時間才能讓它工作(我更喜歡這個而不是不斷地解釋它)。
現在我的實際設置如下所示:在平台A上提供了庫但在平台B上卻沒有。 我知道其他插件經常通過對其進行着色來使用該庫(許多不重定位會導致各種問題)。 因此,為了防止任何沖突,我下載了庫,使用jar-relocator重新定位該jar中的類 ,然后使用反射將其注入到類路徑中。 在這種情況下,如果重新定位,我目前無法使用該庫。 這就是為什么我想在運行時更改代碼中的引用。 它還解釋了為什么我不想更改其他類的引用,因為我不想意外地破壞其他類。 我也認為如果我可以以某種方式使用我自己的ClassLoader ,我不需要將jar注入主ClassLoader因為那樣我就可以告訴ClassLoader使用額外的jar而不必求助於反射。
但正如我所說,根據我的理解,問題與簡化版本相同。

首先,您應該考慮不同的解決方案,因為其他解決方案都比這個更好,所以可能的解決方案:

  1. 只需創建單獨的模塊。
  2. 在編譯時使用一些代碼生成來生成模塊,因此您不需要復制代碼,例如,請查看https://github.com/vigna/fastutil

但如果你真的想以非常臟的方式做到這一點:
使用java代理。 這需要使用jdk jvm或/和其他啟動參數。 如果你想在沒有啟動參數的情況下在運行時執行此操作,你應該使用byte-buddy-agent庫,並且即使沒有來自jdk的適當文件,在Java 8上運行代理也有骯臟的技巧 - 只需手動注入它們,可能也可能在java 9+上,但到目前為止我沒有時間,需要找到一種方法來做到這一點。 你可以在這里看到我的指示https://github.com/raphw/byte-buddy/issues/374#issuecomment-343786107
但是,如果可能的話,最好的方法是使用命令行參數將代理.jar作為單獨的東西附加。
首先要做的是編寫一個類文件轉換器,它將完成您需要的所有邏輯:

public class DynamicLibraryReferenceTransformer implements ClassFileTransformer {
    private final String packageToProcess;
    private final String originalPackage;
    private final String resolvedPackage;

    DynamicLibraryReferenceTransformer(String packageToProcess, String originalPackage, String resolvedPackage) {
        this.packageToProcess = packageToProcess;
        this.originalPackage = originalPackage;
        this.resolvedPackage = resolvedPackage;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) {
        if (! className.startsWith(this.packageToProcess)) {
            return null; // return null if you don't want to perform any changes
        }
        Remapper remapper = new Remapper() {
            @Override
            public String map(String typeName) {
                return typeName.replace(originalPackage, resolvedPackage);
            }
        };
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassRemapper classRemapper = new ClassRemapper(cw, remapper);
        ClassReader classReader = new ClassReader(classfileBuffer);
        classReader.accept(classRemapper, 0);
        return cw.toByteArray();
    }
}

然后你只需要在運行時將它作為java代理應用:

static { 
    Instrumentation instrumentation= ByteBuddyAgent.install();
    // note that this uses internal names, with / instead of dots, as I'm using simple .replace it's good idea to keep that last / to prevent conflicts between libraries using similar packages. (like com/assist vs com/assistance)
    instrumentation.addTransformer(new DynamicLibraryReferenceTransformer("my/pckg/", "original/pckg/", "relocated/lib/"), true);
    // you can retransform existing classes if needed but I don't suggest doing it. Only needed if some classes you might need to transform are already loaded
    // (classes are loaded on first use, with some smaller exceptions, like return types of methods of loaded class are also loaded if I remember correctly, where fields are not)
    // you can also just retransform only known classes
    instrumentation.retransformClasses(install.getAllLoadedClasses());
}

此代碼應盡可能快地運行,例如主類中的靜態代碼塊。

更好的選擇是使用命令行在啟動時將代理包含到JVM:

首先,您需要創建新項目,因為這將是單獨的.jar,並使用Premain-Class: mypckg.AgentMainClass創建清單Premain-Class: mypckg.AgentMainClass ,您將包含在代理.jar meta-inf中。
使用與上面相同的變換器,然后你只需要寫這樣一個非常簡單的代理:

public class AgentMainClass {
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        instrumentation.addTransformer(new DynamicLibraryReferenceTransformer("my/pckg/", "original/pckg/", "relocated/lib/"), true);
    }
}

現在只需將它包含在您的java命令中即可運行應用程序(或服務器) -javaagent:MyAgent.jar
請注意,您可以在main(插件?)。jar中包含代理和清單的代碼,只是確保不會混淆依賴關系,代理的類將使用不同的類加載器加載,因此不要在app和agent之間進行調用,這將是單個.jar中的兩個單獨的東西。

這使用org.ow2.asm.asm-all library和net.bytebuddy.byte-buddy-agent(僅適用於運行時版本)庫。

暫無
暫無

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

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