我想在运行时替换某些方法的内容。
我知道我可以使用 javassist ,但它不起作用,因为我想要增强的类已经被系统 classLoader 加载。
如何在运行时替换方法的内容?我应该尝试卸载课程吗?我怎样才能做到这一点 ?我看到它是可能的,但我无法弄清楚如何做到这一点。
如果可能的话,我想避免使用外部库,我想自己编写代码。
更多信息: - 我想要增强的类包含在一个框架中(在一个jar文件中) - 我的代码实际上是这个框架的插件 - 我的插件运行的框架有自己的 classLoader ,但是这个 classLoader 没有加载自己的类(它将它们委托给系统类加载器) - 我正在使用的框架是 Play 。
感谢您的帮助!
答案 0 :(得分:8)
你可以使用Javaassist以及其他任何字节码工程库来完成它。神奇之处在于 Java Attach API ,它允许程序附加到正在运行的JVM(并修改加载的类)。
可以在com.sun.tools.attach
包中找到它,顾名思义,它特定于Oracle JVM。尽管如此,像jstack
和jmap
这样的JDK工具使用它来支持它们“附加到运行JVM”功能,因此可以肯定地说它就在这里。
Attach API上的文档具有相当的描述性,此Oracle blog post演示了在运行时附加代理。一般来说,归结为:
-javaagent
et al premain
方式制作转发程序
premain
重命名为agentmain
Agent-Class
- 包含)类提供了一个指向agentmain
的清单,并且Can-Retransform-Classes
设置为true
< / LI>
值得庆幸的是,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文件加载类。或类似的东西。