jvm 使Java类在任何类加载器中可见

oogrdqng  于 2022-11-07  发布在  Java
关注(0)|答案(3)|浏览(164)

我正在使用Java代理(Agent.class)转换程序(Program.class)中的方法,转换的方式包括对Agent类的调用。

public Program.getMultiplier()F:
    ALOAD 1
    ALOAD 2
    FDIV
+   INVOKESTATIC Agent.getCustomMultiplier(F)F
    FRETURN

我已经检查了Agent类和Program类的类加载器及其父类,它们的层次结构如下所示:

  • Agent.class:第一个月
  • Program.class:第一个

当程序执行添加的INVOKESTATIC指令时,它会抛出ClassNotFoundException --它找不到Agent类,因为它是由不同的类加载器加载的。
作为一个临时的解决方案,我尝试通过反射强制AppClassLoader成为URLClassLoader的父对象,反射在旧的Java版本中有效,但从Java 12开始就被删除了。
有没有更可靠的方法来确保我的Agent类在任何类加载器中都是可见的?

jogvjijk

jogvjijk1#

您可以使用appendToBootstrapClassLoaderSearch将类添加到引导类加载器中。这使得指定jar文件的类可用于其定义类加载器遵循标准委托模式的所有类。
但这需要将类打包到jar文件中。当您指定代理自己的jar文件时,必须注意通过引导加载器加载的类与通过应用加载器加载的类是不同的,即使它们来自同一个jar文件。此外,引导加载器加载的类不得依赖于其他类加载器加载的类。
如果您的getCustomMultiplier方法应该与正在运行的代理交互,则必须将代理和包含此方法的类分开。

aoyhnmkz

aoyhnmkz2#

让您的代理侦听新ClassLoader的创建,然后将其示例附加到新ClassLoader。
通过这种方式,您可以保留“Agent listens to ClassLoader”接口,即使它现在扩展到您希望Agent侦听的一个平台类加载器之外。

i7uq4tfw

i7uq4tfw3#

你也许可以做一些特定的工作,但是并不是所有的类都是由URLClassLoader的一个示例加载的。任何OSGi项目都不会,大多数Web服务器也使用它们自己的类加载器来支持热加载,等等。
据我所知,没有办法只是随意地更新某个“所有类加载器全局父对象”或注入一个;没有这样父类,即使有,类加载器也可以完全忽略它的父类。
因此一般的回答是:"不"你不能这么做
但是,让我们戴上我们的黑客帽!
你已经是一个代理了。作为代理,你要做的事情之一就是在类被加载时“见证”它们。只需在你的agentmain中得到的Instrumentation示例上调用.addTransformer并注册一个。
当您注意到Program类正在加载时,请执行以下操作:

  • 获取字节码并将其通过ASM、BCEL、Bytecode Buddy或任何其他java“类文件读取器/转换器”框架。
  • 还要从代理的代码中打开一个类(我不会使用Agent本身,我会创建一个名为ProgramAddonMethods或诸如此类的类作为容器--里面的所有内容都是供程序使用/供代理“注入”到该程序中的。
  • ProgramAddonMethods中的 every 静态成员直接添加到Program。执行此操作时,请修改etypename为ProgramAddonMethods的所有访问(包括INVOKESTATIC和读/写字段操作码)的typename,并将其改为目标类的全限定名。
  • 像之前一样注入INVOKESTATIC,但是重写它,使它进入自己的类,因为您刚刚复制了那里的所有静态方法和字段。
  • 然后从转换器中返回修改后的类的字节码。

这100%保证您不可能遇到任何模块或类路径边界问题,并且它可以与任何类加载器抽象一起工作,这是有保证的,但有一些警告:

  • 只是不要试图用示例来做任何事情。让它成为所有静态的方法和字段。如果你必须的话,你可以使用IdentityHashMap来创建假的示例字段(例如,static IdentityHashMap<Foo, String> names;实际上等同于将private String name;添加到Foo类中......当然,除了它有点慢;大概是因为你已经处于一团混乱的反射中,这在这里是可以接受的)。
  • 你的代码必须是“无依赖性的”。它不能依赖任何其他东西,除了java.*之外不能依赖任何库,甚至不能依赖一个helper类。如果你要注入的任务变得复杂,这个想法很快就会失去动力。如果你必须这样做,**为你自己的代理jar创建一个类加载器,使用适当的“线程安全地只初始化一次”保护,并将该负载放在一个包中,这确实具有允许依赖性的好处。

这都是非常复杂的东西,但你似乎已经找到了如何注入INVOKESATIC调用,所以,我认为你知道如何做到这一点。
这正是lombok在eclipse中“修补”一些方法的方法,以确保诸如保存操作、自动格式化和语法高亮显示等功能不会中断-- lombok在适当的地方注入生成的注解的知识,并以这种确切的方式进行,因为eclipse使用了一个名为Equinox的类加载器平台,这使得任何其他解决方案都存在问题。您可以查看它以获得灵感或指导方针。虽然没有特别详细的记载。你现在看到的是:

请注意,下一个方法可能也会引起您的兴趣:patcher的'insert'并不移动方法--它直接将方法体注入到那里(它'inlines')。这需要对堆栈进行一些严格的修饰,并且只建议用于非常简单的单行式方法,对于这个问题来说可能是过度的和不必要的火力。
免责声明:大部分都是我写的。

相关问题