简体   繁体   English

使 Java class 从任何 ClassLoader 可见

[英]Make a Java class visible from any ClassLoader

I'm using a Java Agent (Agent.class) to transform a method in a program (Program.class) in a way that includes a call to the Agent class.我正在使用 Java 代理 (Agent.class) 以包括对代理 class 的调用的方式转换程序 (Program.class) 中的方法。

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

I've inspected the class loaders and their parents of both Agent and Program classes, and their hierarchy looks like this:我检查了 class 加载器及其代理和程序类的父类,它们的层次结构如下所示:

  • Agent.class: AppClassLoader <- PlatformClassLoader <- null Agent.class: AppClassLoader <- PlatformClassLoader <- null
  • Program.class: URLClassLoader <- PlatformClassLoader <- null Program.class: URLClassLoader <- PlatformClassLoader <- null

When the Program executes the added INVOKESTATIC instruction, it throws a ClassNotFoundException -- it cannot find the Agent class as it was loaded by a different class loader.当程序执行添加的INVOKESTATIC指令时,它会抛出 ClassNotFoundException - 它找不到代理 class,因为它是由不同的 class 加载程序加载的。

As a temporary solution, I've tried forcing AppClassLoader to become a parent of URLClassLoader with reflection, which works in older Java versions but has been removed since Java 12.作为一个临时解决方案,我尝试强制AppClassLoader成为具有反射的URLClassLoader的父级,它适用于较旧的 Java 版本,但自 Java 12 以来已被删除。

Is there a more reliable way to make sure my Agent class is visible from any class loader?是否有更可靠的方法来确保我的代理 class 可以从任何 class 加载程序中看到?

You can add classes to the bootstrap class loader using appendToBootstrapClassLoaderSearch .您可以使用appendToBootstrapClassLoaderSearch将类添加到引导 class 加载程序。 This makes the classes of the specified jar file available to all classes whose defining class loader follows the standard delegation pattern.这使得指定 jar 文件的类可用于定义 class 加载器遵循标准委托模式的所有类。

But this requires the classes to be packaged in a jar file.但这需要将类打包在 jar 文件中。 When you specify the Agent's own jar file, you have to be aware that classes loaded through the bootstrap loader are distinct from the classes loaded through the app loader, even when they originate from the same jar file.当您指定代理自己的 jar 文件时,您必须注意,通过引导加载程序加载的类与通过应用程序加载程序加载的类不同,即使它们源自同一个 jar 文件。 Further, the classes loaded by the bootstrap loader must not have dependencies to classes loaded by by other class loaders.此外,引导加载程序加载的类不得依赖于其他 class 加载程序加载的类。

If your getCustomMultiplier method is supposed to interact with the running Agent, you have to separate the agent and the class containing this method.如果您的getCustomMultiplier方法应该与正在运行的代理交互,您必须将代理和包含此方法的 class 分开。

Have your Agent listen to the creation of new ClassLoaders and then attach instances of them to the new ClassLoaders.让您的代理监听新类加载器的创建,然后将它们的实例附加到新的类加载器。

This way you preserve your "Agent listens to ClassLoader" interface, even if it now extends beyond the one platform class loader you expected the Agent to listen to.通过这种方式,您可以保留“代理侦听 ClassLoader”接口,即使它现在超出了您希望代理侦听的一个平台 class 加载程序。

You may be able to do something specific that works for URLClassLoader , but not all classes are loaded by an instance of URLClassLoader.您可能能够执行适用于URLClassLoader的特定操作,但并非所有类都由 URLClassLoader 的实例加载。 Any OSGi project won't, most web servers also use their own classloaders in order to support hot reload, etc.任何 OSGi 项目都不会,大多数 web 服务器也使用自己的类加载器来支持热重载等。

As far as I know there's no way to just casually update some 'global parent of all classloaders' or inject one;据我所知,没有办法随便更新一些“所有类加载器的全局父级”或注入一个; there's no such parent, and even if there was, a classloader is free to ignore its parent entirely.没有这样的父级,即使有,类加载器也可以完全忽略其父级。

Therefore the general answer is: No , you can't do that.因此,一般的答案是:,你不能那样做。

But, let's get our hacking hats on!但是,让我们戴上黑客帽吧!

You're an agent already.你已经是代理了。 One of the things you get to do as agent is to 'witness' classes as they are being loaded.作为代理,您要做的一件事是在加载类时“见证”它们。 Just invoke .addTransformer on the instance of Instrumentation you get in your agentmain and register one.只需在您在agentmain中获得的 Instrumentation 实例上调用.addTransformer并注册一个。

When you notice the Program class being loaded, do the following:当您注意到Program class 正在加载时,请执行以下操作:

  • Take the bytecode and toss it through ASM, BCEL, Bytecode Buddy, or any other java 'class file reader/transformer' framework.获取字节码并通过 ASM、BCEL、Bytecode Buddy 或任何其他 java '类文件读取器/转换器'框架折腾它。
  • Also open up a class from within your agent's code (I wouldn't use Agent itself, I'd make a class called ProgramAddonMethods or whatnot as a container - everything inside is for the program to use / for your agent to 'inject' into that program.还从您的代理代码中打开一个 class (我不会使用Agent本身,我会制作一个名为ProgramAddonMethods的 class 或诸如此类的容器 - 里面的所有内容都是供程序使用/让您的代理“注入”到那个程序。
  • Add every static member in ProgramAddonMethods directly to Program .ProgramAddonMethods中的每个static 成员直接添加到Program As you do so, modify the typename on all accesses (both INVOKESTATIC and the read/write field opcodes) where the etypename is ProgramAddonMethods and make it the fully qualified name of the targeted class instead.当您这样做时,修改所有访问( INVOKESTATIC和读/写字段操作码)的类型名,其中 etypename 为ProgramAddonMethods ,并将其改为目标 class 的完全限定名称。
  • inject the INVOKESTATIC as you already do, but, rewrite it so that it's going to its own class, as you just copied all the static methods and fields over there.像你已经做的那样注入 INVOKESTATIC,但是,重写它,使它进入它自己的 class,因为你刚刚复制了所有的 static 方法和字段。
  • Then return the bytecode of that modified class from your transformer.然后从您的变压器返回修改后的 class 的字节码。

This 100% guarantees you cannot possibly run into any module or classpath boundary issues and it will work with any classloader abstraction, guaranteed, but there are some caveats:这 100% 保证您不可能遇到任何模块或类路径边界问题,并且它可以与任何类加载器抽象一起使用,保证,但有一些警告:

  • Just don't attempt to futz with instance anything.只是不要试图用实例来做任何事情。 Make it all static methods and fields.将其全部设为 static 方法和字段。 You can make fake instance fields using an IdentityHashMap if you must (eg a static IdentityHashMap<Foo, String> names; is effectively identical to adding private String name; to the Foo class.. except it's a bit slower of course; presumably as you're already in a mess o reflection that's acceptable here).如果必须,您可以使用IdentityHashMap制作假实例字段(例如static IdentityHashMap<Foo, String> names;实际上与添加private String name;Foo class.. 除了它当然有点慢;大概就像你'已经处于混乱中,这是可以接受的)。
  • Your code has to be 'dependency free'.您的代码必须是“无依赖的”。 It cannot rely on anything else, no libraries other than java.* , not even a helper class.它不能依赖其他任何东西,除了java.*之外没有其他库,甚至没有帮助程序 class。 This idea quickly runs out of steam if the job you're injecting becomes complicated.如果您要注入的工作变得复杂,这个想法很快就会失去动力。 If you must, make a classloader for your own agent jar using the appropriate 'thread-safely initialize it only once' guards, and have that load in a bundle that does have the benefit of allowing dependencies.如果必须,请为您自己的代理 jar制作一个类加载器,使用适当的“线程安全地仅初始化一次”防护,并将该加载放在一个确实具有允许依赖项的好处的包中。

This is all highly complicated stuff but you appear to have already worked out how to inject INVOKESTATIC calls, so, I think you know how to do this.这都是非常复杂的东西,但您似乎已经想出了如何注入 INVOKESTATIC 调用,所以,我想您知道如何做到这一点。

This is precisely what lombok does to 'patch' some methods in eclipse to ensure that things like save actions, auto-formatting, and syntax highlighting don't break - lombok injects knowledge of generated notes where appropriate and does it in this exact manner because eclipse uses a classloader platform called Equinox which makes any other solution problematic.这正是 lombok 在 eclipse 中“修补”某些方法以确保保存操作、自动格式化和语法突出显示等操作不会中断 - lombok 在适当的地方注入生成的注释的知识并以这种精确的方式进行操作,因为eclipse 使用名为 Equinox 的类加载器平台,这使得任何其他解决方案都存在问题。 You can look at it for inspiration or guidelines, though it's not particularly well documented.您可以查看它以获取灵感或指导,尽管它的文档记录并不特别好。 You're looking in particular at:您正在特别关注:

Note that the next method may also interest you: lombok.patcher's 'insert' doesn't move the method - it injects the body of the method directly in there (it 'inlines').请注意,您可能也对下一个方法感兴趣:lombok.patcher 的“插入”不会移动该方法 - 它直接将方法体注入其中(它“内联”)。 This requires some serious finagling of the stack and is only advised for extremely simple one-liner-esque methods, and probably is excessive and unneccessary firepower for this problem.这需要对堆栈进行一些认真的处理,并且仅建议用于极其简单的单线式方法,并且对于此问题可能是过多且不必要的火力。

DISCLAIMER: I wrote most of that.免责声明:我写了大部分内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM