For my instrumentation tool, I want to provide a wrapping ClassLoader that is used to start a main method after specific classes have been instrumented. My ClassLoader should load instrumented versions of certain classes. But for Jetty and JUnit, this approach is severly limited because they build their own classloading hierarchy.
I don’t want to pass VM arguments, so I can’t change the SystemClassLoader. But I can force-feed it with my classes by using reflection to make ClassLoader.defineClass(String, byte[], int, int) public.
ClassLoader scl = ClassLoader.getSystemClassLoader();
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
for (String binaryName : classNamesToLoad) {
byte[] bytecode = this.declaredClasses.get(binaryName);
defineClass.invoke(scl, binaryName, bytecode, 0, bytecode.length);
}
defineClass.setAccessible(false);
This is just great – but there’s one problem left: If some of my classes inherit from or contain other classes, they have to be loaded in the right order because the SystemClassLoader loads all classes the current one depends on – and would load the uninstrumented version.
Here is an example with some (poorly named) classes and the order they would have to be loaded in:
A
A.A extends B.A
B
B.A extends B.C
B.C
would have to be loaded in order
B
B.C
B.A
A
A.A
if I want to load only the instrumented version.
Is there an easy way out – e.g. a “setSystemClassLoader” method I didn’t spot yet?
A workaround by which I wouldn’t need to manipulate the SystemClassLoader?
Or do I really have to do a full transitive dependency analysis starting on the classes I want to load to determine the right order (and in this case: is there any “prior art” I can work with)?
Thanks!
Looks like there’s no way around the transitive dependency analysis.
I solved it this way, and I really hope someone can profit from this implementation:
It’s used like this: on a
DependencyDetector, you calladd(ClassNode)to add aClassNodeand all its dependencies (all classes it extends or implements or is contained by). When you are done building the dependency tree, you callgetClassesToLoad()to retrieve all dependencies as aString[]containing the binary names in the necessary order. You can also just ask for a subset of all added classes and their dependencies by specifying the binary names as a parameter ofgetClassesToLoad(...).Now, when I instrument classes, I also add the
ClassNodeto theDependencyDetectorand can retrieve everything I need to pass it into a method like this:which relies on
That’s pretty much it and it works great in every situation I encountered so far.