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

时间:2012-07-31 21:40:37

标签: java playframework runtime classloader javassist

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

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

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

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

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

感谢您的帮助!

2 个答案:

答案 0 :(得分:8)

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

可以在com.sun.tools.attach包中找到它,顾名思义,它特定于Oracle JVM。尽管如此,像jstackjmap这样的JDK工具使用它来支持它们“附加到运行JVM”功能,因此可以肯定地说它就在这里。

Attach API上的文档具有相当的描述性,此Oracle blog post演示了在运行时附加代理。一般来说,归结为:

  • 使用-javaagent et al
  • 以“常规”premain方式制作转发程序
  • premain重命名为agentmain
  • 创建一个临时JAR文件,其中包含您的代理类,并且向您的代理(Agent-Class - 包含)类提供了一个指向agentmain的清单,并且Can-Retransform-Classes设置为true < / LI>
  • 获取目标JVM的PID(可能是相同的进程),并将临时jar附加到它上面

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

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

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

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


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

答案 1 :(得分:4)

正常的ClassLoaders在定义后不支持取消定义或修改类。因此,插件无法修改框架的行为,除非该框架为此类自定义提供了挂钩。

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

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

更新回复评论:
为了创建一个类的Loader来构思某些类,你必须覆盖它的loadClass方法。如果您的许可允许使用GPL代码,您可以在默认实现中查看how OpenJDK does this。您只需要为那些您不想隐藏的类推迟父类加载器。

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