I have the following code. I want to change the say method of the hello class. I use javassist. I have the following error.
public class TestJavasisit {
/**
* @param args the command line arguments
* @throws java.lang.Exception
*/
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
// version original
Hello h1 = new Hello();
h1.say();
CtClass cc = pool.get("testjavasisit.Hello");
cc.defrost();
CtMethod m = cc.getDeclaredMethod("say");
m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
cc.writeFile(".");
cc.toClass();
// version modifie
Hello h2 = new Hello();
h2.say();
}
}
The hello class :
public class Hello {
public void say() {
System.out.println("Hello");
}
}
The error message:
run:
Hello
Exception in thread "main" javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "testjavasisit/Hello"
package testjavasisit;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class TestJavasisit {
/**
* @param args
* the command line arguments
* @throws java.lang.Exception
*/
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
// version original
Hello h1 = new Hello(); // remove this line
h1.say(); // remove this line
CtClass cc = pool.get("testjavasisit.Hello");
cc.defrost();
CtMethod m = cc.getDeclaredMethod("say");
m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
cc.writeFile(".");
// This throws a java.lang.LinkageError ... attempted duplicate class definition for name: ...
cc.toClass();
// version modifie
Hello h2 = new Hello();
h2.say();
}
}
If you remove the following 2 lines, then it will run successfully.
// Hello h1 = new Hello();
// h1.say();
Output:
Hello.say():
Hello
When first time you use Hello h1 = new Hello();
, the classloader loads the Hello class.
After that when you again try to load the Hello class by using cc.toClass();
, this error comes.
Rafael Winterhalter told the reason and some way of solutions in this link as
cc.toClass()
takes a loaded class[Hello
] and redefines this very same class without changing its name. After this redefinition, you attempt to load the altered class one more time. This is however not possible in Java where any ClassLoader can only load a class of a given name one single time.To overcome your problem, you have different choices:
- Create a subclass of the argument class (or use interfaces) which uses a random name. The subclass is then type compatible to your argument class but is never loaded.
- Use the Instrumentation API to redefine your loaded class at runtime.
- Make sure that the input class and the output class are loaded with different ClassLoaders. (not recommended)
In tomcat, they have solved the issue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With