【问题标题】:Replace content of some methods at runtime在运行时替换某些方法的内容
【发布时间】:2012-07-29 18:26:34
【问题描述】:

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

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

如何在运行时替换方法的内容?我应该尝试卸载课程吗?我怎样才能做到这一点 ?我看到这是可能的,但我不知道该怎么做。

如果可能,我想避免为此使用外部库,我想自己编写代码。

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

感谢您的帮助!

【问题讨论】:

  • 玩!是开源的。您可以直接在源代码中更改方法并构建自己的 jar 来使用。
  • 你能写一个你想改变的类的子类吗?
  • @Jeffrey 我可以,但我想为社区写一个“可用”的插件,所以我想避免修改源代码
  • @Code-Guru 不,我不能,这是一个静态类。所以它的方法是直接通过类调用的

标签: java playframework runtime classloader javassist


【解决方案1】:

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

它可以在com.sun.tools.attach 包中找到,顾名思义,它是特定于Oracle JVM 的。尽管如此,像 jstackjmap 这样的 JDK 工具使用它来支持它们的“附加到正在运行的 JVM”特性,所以可以肯定地说它会继续存在。

Attach API 上的文档描述性很强,这个Oracle blog post 演示了在运行时附加代理。一般来说,它归结为:

  • 使用premain 等“常规”-javaagent 方式制作重新转换程序
  • premain 重命名为agentmain
  • 创建一个临时 JAR 文件,其中包含您的代理类并有一个清单将 Agent-Class 指向您的代理(包含agentmain)类,并将 Can-Retransform-Classes 设置为 true
  • 获取目标 JVM(可能是同一进程)的 PI​​D,并将临时 jar 附加到它

值得庆幸的是,API 无需您做太多工作就可以做到这一点,但如果您在运行时生成 JAR,打包代理所需的所有类可能会有点棘手。

我希望包含一个演示代理来演示在运行时附加分析器,但它最终太长而无法发布。尽管如此,我还是把它放在了Github repo

这种方法的

一个警告是,它使您的程序依赖于 JDK 附带的 tools.jar,而 JRE 中不存在该 tools.jar。您可以通过将tools.jar 与您的应用程序一起发送(或提取到您的应用程序中)来解决此问题,但您仍然需要在您的应用程序中提供Attach API 所需的attach 本机库。我已经在上面链接的存储库中包含了我可以找到的所有平台的库,尽管您也可以自己获取它们。

根据您的用例,这可能是理想的,也可能不是理想的。但它确实有效!


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

【讨论】:

    【解决方案2】:

    普通类加载器不支持取消定义或修改已定义的类。所以插件不能修改框架的行为,除非框架为这种定制提供了钩子。

    您可以创建一个自定义类加载器,该加载器隐藏其父类加载器中的一些类,而是重新定义它们,添加您可能想要的任何工具。但是框架在插件之前被加载,并且会使用它自己的类加载器来解析类。所以它会继续使用类的未检测版本。

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

    更新回复评论:
    为了创建一个包含一些类的类加载器,你必须重写它的loadClass 方法。如果您的许可允许使用 GPL 代码,您可以查看默认实现中的 how OpenJDK does this。对于那些不想隐藏的类,您只需要遵循父类加载器即可。

    隐藏父版本后,您仍然需要修改类。也许BCEL class loader 可以帮助你。或者您从包含修改版本的 jar 文件中加载该类。或者类似的东西。

    【讨论】:

    • 我不能“先到”。但是,您说了一些关于“隐藏”某些课程的内容。你的意思是我可以创建另一个类加载器,让它从框架的类加载器继承,当我想要增强的类被要求时,我自己加载它? (如果问另一个类,我让超类加载它?)。它会起作用吗?如果是,我该怎么做才能加载课程?在其他地方有这个类的副本并加载这个副本?找到类,增强它并返回新创建的类?
    • 我尝试在play框架定义的classLoader中添加log,但在使用我要增强的类时,似乎没有调用(loadClass方法)。是因为它是从系统类加载器加载的吗?那我该怎么办?是不是因为它是一个只有静态方法的类?
    • @FabienHenon,您可以使用Class.getClassLoader() 查询每个类加载它的类加载器。您可以使用它来查看谁加载了相关类。我想这将是系统类加载器。正如我所说,您必须在框架之前决定使用哪个类加载器。
    • 所以如果我不能替换框架之前的classLoader就不能替换方法的内容?我不能只设置一个新的装载机?还是释放系统加载器?
    • @FabienHenon,任何新的加载器只会影响你通过它显式加载的类,或者它们引用的类。所以你也必须更换整个框架。据我了解,这不是您想要的。您想到的这种修改在诸如 python 之类的语言中称为“猴子补丁”。关于java,有a question,但我怀疑该解决方案也适用于您的情况。
    猜你喜欢
    • 1970-01-01
    • 2013-12-13
    • 1970-01-01
    • 2013-07-24
    • 1970-01-01
    • 2012-02-27
    • 1970-01-01
    • 2018-08-10
    • 2013-10-03
    相关资源
    最近更新 更多