22

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

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

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

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

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

谢谢您的帮助 !

4

2 回答 2

8

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

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

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

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

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

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

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

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


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

于 2012-09-06T20:16:36.490 回答
4

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

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

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

更新回复评论:
为了创建一个包含某些类的类加载器,您必须覆盖其loadClass方法。如果您的许可允许使用 GPL 代码,您可以查看OpenJDK在默认实现中是如何做到这一点的。对于那些不想隐藏的类,您只需要遵循父类加载器即可。

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

于 2012-07-31T21:56:47.300 回答