In my use case I generate some classes dynamically and load them with a custom classloader. The classloader is very standard and does something like this (simplified):
public class MemoryClassLoader extends ClassLoader { @Override public Class<?> findClass(String name) throws ClassNotFoundException { javaClass = getGeneratedClass(name); if(javaClass != null) { byte[] bytes = javaClass.getBytes(); return defineClass(name, bytes, 0, bytes.length); }else{ throw new ClassNotFoundException(); } } }
This works great without the Enhancer Agent, but once the Agent is enabled it is causing issues and I get errors like this:
Java.lang.LinkageError: loader (instance of rsl/core/classloaders/MemoryClassLoader): attempted duplicate class definition for name: "rsl/models/shared/selectors/ImageSelector" at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
I assume this is because the Agent intercepts my call to defineClass(), makes the enhancement to the class and loads the modified version in memory first. However, the main issue is that the Agent seems to call back to my classloader (loadClass()/findClass()/...) as part of the enhancement process, resulting in a recursive call to my custom findClass() method. This means that the LinkageError is thrown before my first call to MemoryClassLoader.findClass() even returns, making it hard to implement a workaround. A simplified call stack to illustrate what I mean:
MYLOADER: MemoryClassLoader.loadAllGeneratedClasses() { MYLOADER: MemoryClassLoader.findClass("myGeneratedClass") { MYLOADER: defineClass("myGeneratedClass") { OBJECTDB_AGENT: performEnhancementProcess() { OBJECTDB_AGENT: MemoryClassLoader.findClass("myGeneratedClass") { MYLOADER: defineClass("myGeneratedClass") { ERROR, attempted duplicate class definition for name: "myGeneratedClass" } } } } } }
The most obvious solution would be that my findClass() method checks if the class is already loaded (e.g. by the agent) to prevent calling defineClass() twice, but the standard ways for searching classes (e.g. Class.forName() or the Reflections library) claim that the conflicting classes don't exist anywhere, so I'm not sure what to do now. It's weird because I can't seem to detect the classes on the classpath even at the moment when the LinkageError is thrown.
So my questions are:
- Can I detect if the Agent has already loaded the modified class so that I can return that instead?
- Does the classloader used by the enhancement agent respect the classloading hierarchy? E.g. Can I delegate the class lookup to the agent's classloader to get the new modified version and return that?
- Any other ideas on how to approach this issue?
Thank you for your time.