Bra*_*one 12 java reflection runtimemodification java-8
因此,首先,我知道您可以在不同的构建系统上使用各种影子插件将已编译jar的所有引用重定位。我知道它的工作原理,并且已经在使用它。但是我遇到了一个问题,我在编译时无法做到这一点。
我将简化情况,以便更容易理解(但如果您感到好奇,我将在底部解释整个情况)。
我正在为两个不同(但相似)的系统(一个罐子,全部)编写一个插件。这些平台负责启动基础软件并加载/启动所有插件(因此,我无法控制应用程序,包括启动参数)。
平台A为我提供了一个库(我们称之为com.example.lib)。平台也是如此B。但它决定将其迁移到org.b.shadow.com.example.lib。
现在,在插件的核心代码(在两个平台上使用的代码)中,我都使用了库。现在,虽然我可以检测到在哪个平台上,但是我目前不知道如何在运行时将代码中的所有引用重写到库中,因此它可以在platform上运行B。
从我发现的结果来看,似乎需要使用自定义ClassLoader来实现这一点。这里的问题是我不知道我可以让运行时使用我的自定义ClassLoader。还是从哪里真正开始。
重要的一点是,这些重定位可能只会影响我的程序包中的类中的引用(me.brainstone.project例如)。
我使用(并已着色)的另一个依赖项使用ASM和ASM Commons,因此,如果有可能使用它们,那就太好了!
因此总而言之。我想在运行时仅将我类中的引用(对其他类)重新定位。
编辑:
在我的整个(原始)帖子中,我只谈论过一个库,但我想指出的是,我将针对服务器库进行此操作。而做这些事情需要我付出很大的努力(允许我为每个库(类或部分)编写包装器都被认为是一项巨大的努力),而这并不是我想要的。相反,我想要一种解决方案,它需要最少的插件才能将新库添加到混合中。
现在,这里是我的设置的详细说明。
拳头我想作为序言,我知道我可以为不同的平台创建两个不同的jar。我已经在这样做了。但是,由于令人惊讶的是,很多人似乎无法弄清楚这一点,而我又厌倦了一遍又一遍地解释它(那些不愿阅读文档来挽救生命的人),我想提供一个即使这意味着我需要花费大量的时间来使它工作,也要使用一个罐子(相对于不断地解释它,我更喜欢这样做)。
现在,我的实际设置如下所示:在平台A上提供了库,但在平台上提供了B不是。我知道其他插件经常通过将其阴影化来使用该库(许多未重新定位会导致各种问题)。因此,为了避免发生任何冲突,我下载了该库,使用jar-relocator在该jar中重新放置类,然后使用反射将其注入到类路径中。在这种情况下,如果库已重定位,我目前无法使用它。这就是为什么我想在运行时更改代码中的引用。它还说明了为什么我不想更改其他类的引用,因为我不想意外破坏其他插件。我也认为,如果我能以某种方式使用自己的东西ClassLoader,我不会ClassLoaderClassLoader
但是正如我所说,据我了解,该问题与简化版本中的问题相同。
Got*_*nal 11
首先,您应该考虑不同的解决方案,因为其他所有解决方案都比该解决方案更好,因此可能的解决方案是:
但是,如果您真的想用非常肮脏的方式做到这一点:
使用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();
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您只需要在运行时将其作为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());
}
Run Code Online (Sandbox Code Playgroud)
该代码应尽快运行,就像在主类中的静态代码块中一样。
更好的选择是在启动时使用命令行将代理包括到JVM中:
首先,您将需要创建新项目,因为这将是单独的.jar,并创建清单 Premain-Class: mypckg.AgentMainClass,并将其包含在agent的meta-inf中.jar。
使用与上述相同的转换器,然后您只需要编写非常简单的代理,如下所示:
public class AgentMainClass {
public static void premain(String agentArgs, Instrumentation instrumentation) {
instrumentation.addTransformer(new DynamicLibraryReferenceTransformer("my/pckg/", "original/pckg/", "relocated/lib/"), true);
}
}
Run Code Online (Sandbox Code Playgroud)
现在,只需将其包含在您的Java命令中即可运行该应用程序(或服务器)-javaagent:MyAgent.jar。
请注意,您可以在主(插件?)。jar中包含代理和清单的代码,只是请确保不要混淆依赖项,代理的类将使用不同的类加载器加载,因此请不要在应用和代理之间进行调用,这将是单个.jar中的2个单独的内容。
这使用org.ow2.asm.asm-all库和net.bytebuddy.byte-buddy-agent(仅适用于运行时版本)库。