在运行时替换某些方法的内容

我想在运行时替换某些方法的内容。

我知道我可以使用javassist来实现这一点,但它不起作用,因为我想增强的类已经由系统classLoader加载。

我该怎么做,在运行时替换方法的内容?我应该尝试卸载类吗?我该怎么做?我看到这是可能的,但我不知道该怎么做。

如果可能的话,我想避免使用外部库,我想自己编码。

更多信息: - 我想增强的类包含在一个框架中(在jar文件中) - 我的代码实际上是这个框架的插件 - 我的插件运行的框架有自己的classLoader,但是这个classLoader不加载自己的类(它将它们委托给系统类加载器) - 我正在使用的框架是Play

感谢您的帮助!


答案 1

你可以使用Javaassist以及任何其他字节码工程库来做到这一点。神奇之处在于Java Attach API,它允许程序附加到正在运行的JVM(并修改加载的类)。

它可以在com.sun.tools.attach软件包中找到,顾名思义,它是特定于Oracle JVM的。尽管如此,JDK工具喜欢并使用它来支持他们的“附加到正在运行的JVM”功能,所以可以肯定地说它将继续存在。jstackjmap

Attach API 上的文档具有相当的描述性,这篇 Oracle 博客文章演示了在运行时附加代理。一般来说,它归结为:

  • 使重构程序以“常规”方式进行,使用等-javaagentpremain
  • 重命名为premainagentmain
  • 创建一个临时 JAR 文件,其中包含您的代理类,并具有指向您的代理(包含)类的清单,并将其设置为Agent-ClassagentmainCan-Retransform-Classestrue
  • 获取目标 JVM 的 PID(可能具有相同的进程),并将临时 jar 附加到该 JVM 上

值得庆幸的是,API可以在您不需要太多工作的情况下完成此操作,但是如果您在运行时进行JAR生成,那么打包代理所需的所有类可能会有点棘手。

我希望包含一个演示代理,演示在运行时附加探查器,但最终它太长而无法发布。尽管如此,我已经把它放在Github存储库中。

这种方法的一个警告是,它使您的程序依赖于随JDK一起提供的程序,并且JRE中不存在。您可以通过随应用程序一起交付(或提取)来解决此问题,但您仍然需要随应用程序一起提供 Attach API 所需的本机库。我已经包含了我在上面链接的存储库中可以找到的所有平台的库,尽管您也可以自己获取它们。tools.jartools.jarattach

根据您的使用案例,这可能是也可能不理想。但它肯定有效!


这在问题中并不清楚,但是如果您希望做的是在运行时用您自己的类完全“热插拔”,则不需要使用任何字节码操作库。相反,您可以单独编译类(确保相同的包、类名等),并在目标类上调用时简单地返回新类的字节。transform


答案 2

普通的类加载器不支持在类被定义后取消定义或修改类。因此,插件无法修改框架的行为,除非该框架为此类自定义提供钩子。

您可以创建一个定制类装入器,该装入器从其父类装入器中隐藏一些类,而是重新定义它们,添加您可能想要的任何检测。但是框架在插件之前加载,并将使用自己的类加载器解析类。因此,它将继续使用类的未说明版本。

避免这种情况的唯一合理方法(我能想到的)是首先出现:如果你的代码首先启动,它可以引入一个类加载器来加载框架。但这意味着你必须有某种方法将你的代码作为框架的包装器放入链中。不确定这在您的情况下是否可行。

在回复注释时更新:
为了创建一个类加载器来ides某些类,您必须重写其loadClass方法。如果您的许可允许使用GPL代码,您可以查看OpenJDK如何在默认实现中执行此操作。对于那些不想隐藏的类,您只需遵从父类装入器。

隐藏父版本后,您仍必须修改类。也许 BCEL 类装入器可以为您提供帮助。或者,从包含已修改版本的 jar 文件中加载该类。或类似的东西。


推荐