是否有可能在Java中进行猴子修补,如果没有替代方案?

这是8年前在这里被问到的,从那时起已经过去了8年。 我想再次问这个问题,看看是否有人开发了一个猴子修补的框架,工具或库。

基本上我需要它是一个java应用程序,我应用自己的补丁。 由于这个项目由另一个团队维护,我希望能够保留/应用我制作的任何补丁,以及他们制作的补丁。

有许多技术可能适用于此处,但您的问题过于模糊,无法将其缩小为单一答案。

“猴子修补”在字面意义上表示它在Ruby中使用(即“在运行时替换类的方法”,参见例如[1] )可以使用“Java代理”和“转换”API,但它更难比在Ruby中。

源代码修补

我需要它来为我应用自己的补丁的java应用程序

如果有一个你有源代码的应用程序,比如git ,那么你可以分叉他们的项目,应用你自己的补丁并构建一个修改后的版本。

我希望能够继续将我制作的任何补丁应用到他们制作的补丁中。

如果你在一个分支上创建你的补丁,那么使用git可以很容易地将“上游”项目的任何未来变化引入你的分支,并构建一个新的修改版本。

通过类路径优先级在类加载时替换类

一种更接近Monkey Patching的简单技术是从目标应用程序编译单个类,并进行修改,并将其放在类路径上,而不是原始JAR。 (这个答案在旧的Monkey Patching q中有所涉及: https : //stackoverflow.com/a/381240/8261 )

JVM按名称加载所有类,并将使用它在任何类的类路径中找到的第一个类文件,因此您可以从要修改的项目中逐个替换类。 如果您拥有目标项目的源,则将其逐个文件复制到您的应用程序中,然后将修补程序应用于Java源代码。

(您需要使用此方法手动应用任何未来的上游更改。)

在类加载时转换类或通过JVM代理随时“重新转换”方法体

JVM有一个名为“ Java Agents ”的API,它允许您注册代码以在加载时修改类。

还有一个“重新转换 ”API,允许您更改已加载的类的定义。 JRebel使用它来更新正在运行的应用程序中的代码。 Ruby的猴子补丁更加局限于你不能添加或删除方法(你可以改变方法体)。

例如, https://github.com/fommil/class-monkey使用此机制来“猴子补丁”JVM错误。

如果您创建自己的类加载器,几乎可以做任何事情。

这篇关于在运行时重新加载类的文章并不完全是你正在做的事情,但是这些信息对于你想要做的事情非常有用。

关于更改默认类加载器的stackoverflow问题/答案也会有所帮助。

通过使用MonkeyPatchClassLoader自定义类加载器加载类的不同版本,您可以让您的类版本执行不同的操作,或者他们可以将某些任务委派给类的原始版本(也就是说,您可以使用新版本该类的版本封装了该类的旧版本)。

Java思考API也可能派上用场,具体取决于您想要更改的内容以及您希望如何更改它。

下面是关于类加载的第一个链接的示例代码片段,只是为了让您思考这个方向:

 // Every two seconds, the old User class will be dumped, a new one will be loaded and its method hobby invoked. public static void main(String[] args) { for (;;) { Class userClass = new DynamicClassLoader("target/classes") .load("qj.blog.classreloading.example2.ReloadingContinuously$User"); ReflectUtil.invokeStatic("hobby", userClass); ThreadUtil.sleep(2000); } } 

要更改默认的类加载器:

 java -cp Example.jar -Djava.system.class.loader=example.ClassLoader example.ClassA 

这是一个从反思开始的好地方 。

如果这还不足以让你到达你想要的地方,那就用这些工具失败/难以克服的障碍来更新你的问题,也许我们可以克服它们。

“纯粹的”猴子补丁需要动态语言,而Java则不然。 所以在技术上它是不可能的。 进行运行时更改的最佳选择是进行字节码操作。 用于此类工作的通用库是ASM 。

我想到了两个选择,尽管可能有更多的选择是“接近”,尽管不是那样:

  • 如果您可以使用OSGi容器,那些可以动态加载,重新加载和卸载jar并基于此激活。 描述OSGi值得单独讨论:请参阅OSGi解决了什么? 。

  • Netflix网关应用程序Zuul使用一种技术来轮询文件系统以获取filter代码并将其用于运行时。 它还将这些变化存储到Cassandra中。 在内部,使用DynamicCodeCompiler实现GroovyCompiler的FilterLoader 。 所以它实际上为任务编译Groovy文件,然后将它们加载到内存中。

对于实际的猴子修补,reflectionapi具有所有限制,几乎就是你所拥有的。 我觉得这是件好事。

是的,你可以在运行时修补Java类。 您可以使用sun.misc.Unsafe在运行时访问私有字段,然后运行类查找器并更改引用以将它们设置为您想要的任何内容。 在这里写了一篇博文。

https://tersesystems.com/blog/2014/03/02/monkeypatching-java-classes/

我也回复了原来的问题,所以我不确定你为什么不提这个问题。 它是ssl-config的一部分:

https://github.com/lightbend/ssl-config/blob/master/ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/MonkeyPatcher.scala