You may be able to do something specific that works for URLClassLoader
, but not all classes are loaded by an instance of URLClassLoader. Any OSGi project won't, most web servers also use their own classloaders in order to support hot reload, etc.
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.
When you notice the Program
class being loaded, do the following:
- Take the bytecode and toss it through ASM, BCEL, Bytecode Buddy, or any other java 'class file reader/transformer' framework.
- 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.
- Add every static member in
ProgramAddonMethods
directly to 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.
- 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.
- Then return the bytecode of that modified class from your transformer.
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:
- Just don't attempt to futz with instance anything. Make it all static methods and fields. You can make fake instance fields using an
IdentityHashMap
if you must (e.g. 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).
- Your code has to be 'dependency free'. It cannot rely on anything else, no libraries other than
java.*
, not even a helper 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.
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.
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. 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'). 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.