Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/378.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java-在运行时重新定位引用_Java_Reflection_Java 8_Runtimemodification - Fatal编程技术网

Java-在运行时重新定位引用

Java-在运行时重新定位引用,java,reflection,java-8,runtimemodification,Java,Reflection,Java 8,Runtimemodification,首先,我知道您可以在不同的构建系统上使用不同的影子插件重新定位编译后的jar的所有引用。我知道它是如何工作的,并且已经在使用它了。然而,我遇到了一个问题,我不能在编译时这样做 我会简化我的情况,以便更容易理解(但我会在底部解释全部情况,以防您好奇)。 我正在为两个不同(但相似)的系统(一个jar for all)编写一个插件。这些平台负责启动底层软件和加载/启动所有插件(因此我无法控制应用程序,包括启动参数)。 平台A为我提供了一个库(我们称之为com.example.lib)。平台B也是如此。

首先,我知道您可以在不同的构建系统上使用不同的影子插件重新定位编译后的jar的所有引用。我知道它是如何工作的,并且已经在使用它了。然而,我遇到了一个问题,我不能在编译时这样做

我会简化我的情况,以便更容易理解(但我会在底部解释全部情况,以防您好奇)。
我正在为两个不同(但相似)的系统(一个jar for all)编写一个插件。这些平台负责启动底层软件和加载/启动所有插件(因此我无法控制应用程序,包括启动参数)。
平台
A
为我提供了一个库(我们称之为
com.example.lib
)。平台
B
也是如此。但它决定将其重新定位到
org.b.shadow.com.example.lib

现在,在插件的核心代码(两种平台上使用的代码)中,我使用了库。现在,虽然我可以检测我在哪个平台上,但我目前不知道如何在运行时将代码中的所有引用重写到库中,以便它在平台
B
上工作

从我发现的情况来看,似乎我需要使用一个定制的
ClassLoader
来实现这一点。这里的问题是,我不知道我可以让运行时使用我的自定义
类加载器
。或者真正从哪里开始。
重要的一点是,这些重新定位可能只会影响我的包中的类中的引用(例如,
me.brainsone.project

我使用的另一个依赖项(和阴影部分)使用ASM和ASM Commons,所以如果可以使用它们,那将是惊人的

总而言之。我希望在运行时仅在我的类中选择性地重新定位引用(到其他类)

编辑

虽然在我的整个(原始)帖子中,我只提到过一个库,但我想指出的是,我将为多个库做这件事。在那里做一些需要我付出巨大努力的事情(为每一个图书馆(班级或部门)写包装纸都会被认为是一个巨大的努力),允许我使用图书馆并不是我要找的东西。相反,我想要一个解决方案,该解决方案要求在混合中添加新库时需要最少的附加项


下面是对我的设置的更详细的解释。
首先,我想先说一句,我知道我可以为不同的平台创建两个不同的jar。我已经在这么做了。但是,令人惊讶的是,许多人似乎无法理解这一点,我已经厌倦了一遍又一遍地解释它(这些人不愿意阅读文档来拯救自己的生命),所以我只想为这两种人提供一个罐子,即使这意味着我需要花大量时间让它工作(我更喜欢这样,而不是不断地解释它)。
现在,我的实际设置是这样的:在平台
A
上提供了库,但在平台
B
上没有。我知道其他插件经常通过着色来使用库(许多插件没有重新定位,导致各种问题)。因此,为了防止任何冲突,我下载了库,用重新定位jar中的类,然后使用反射将其注入类路径。在这种情况下,如果重新定位库,我当前无法使用该库。这就是为什么我希望在运行时更改代码中的引用。这也解释了为什么我不想更改其他类的引用,因为我不想意外破坏其他插件。我还认为,如果我能以某种方式使用我自己的
ClassLoader
,我就不需要将JAR注入主
ClassLoader
,因为这样我就可以告诉
ClassLoader
使用额外的JAR,而不必求助于to反思。

但正如我所说的,从我的理解来看,这个问题与简化版本中的问题是一样的。

首先,你应该考虑不同的解决方案,因为每个其他解决方案都比这个好,所以可能的解决方案:

  • 只需创建单独的模块
  • 在编译时使用一些代码生成来生成这些模块,这样您就不需要复制代码,例如
  • 但是,如果你真的想用非常肮脏的方式做这件事:
    使用java代理。这需要使用jdk jvm或/和其他启动参数。如果您想在运行时不使用启动参数就使用byte buddy代理库,而且java 8上还有一个肮脏的技巧,即即使没有来自jdk的正确文件,也可以在运行时运行代理—只需手动注入它们,这也是可能的在java 9+上,但到目前为止,我没有时间,需要找到一种方法来实现这一点。您可以在这里查看我的说明
    但是,如果可能的话,最好的方法是使用命令行参数将agent.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();
        }
    }
    
    这段代码应该尽快运行,就像主类中的静态代码块一样

    更好的选择是在启动时使用命令行将代理包含到JVM中:

    首先,您需要创建一个新项目,因为它将是独立的.jar,并使用
    Premain Class:mypckg.agentMailass
    创建清单,该清单将包含在agent
    .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(plugin?).jar中包含代理代码和清单,但请确保不要混淆依赖项,代理的类将使用不同的类装入器装入,所以不要在应用程序和代理之间进行调用,这将是single.jar中的两个独立内容

    这使用org.ow2.asm.asm-all库和net.bytebuddy.byte-buddy-age
    public class AgentMainClass {
        public static void premain(String agentArgs, Instrumentation instrumentation) {
            instrumentation.addTransformer(new DynamicLibraryReferenceTransformer("my/pckg/", "original/pckg/", "relocated/lib/"), true);
        }
    }