I'm currently messing around with javassist and stumpled across this problem. I'm trying to change a field type using javassist. However, when the code is trying to access this field after i changed it, the result is a NoSuchFieldException.
So here's the class i'm trying to change:
public class AlterMe {
private ClassA someField;
public void doSomething() {
someField.doSomething(Object someArg);
}
}
And here's the part of my ClassLoader doing the actual change:
//...Left out all the boilerplate code for testing if its the right class
Class ctClass = ClassPool.getDefault().get(name);
ctClass.getField("SomeField").setType(ClassPool.getDefault().get("the.replacement.ClassB");
// I also do the instantiation inside the constructors
for (CtConstructor constructor : ctClass.getConstructors()) {
constructor.insertBefore("someField = new the.replacement.ClassB();");
}
I also tried several other ways of changing the field type. For example I tried removing the field and adding one by the same name or redirecting the calls to someField to someFieldNew which I added before using the right type. So when I run doSomething() now, I get this exception:
Exception in thread "main" java.lang.NoSuchFieldError: someField
For completeness, I'm using source level 1.7 with javassist version 3.21.0-GA.
I hope anybody can help with this, because I sure am stuck here.
Ok, here's the solution I finally found. It's not nice but it works. Instead of replacing the type of
someField
I added a field with the correct type and used an ExprEditor to change all the method calls. Here's the code:
CtField field = new CtField(ClassPool.getDefault().get("the.replacement.classB"), "classB", ctClass);
ctClass.addField(field, CtField.Initializer.byExpr("new the.replacement.classB()"));
for (CtMethod method : ctClass.getMethods()) {
method.instrument(new ExprEditor() {
public void edit(MethodCall methodCall) throws CannotCompileException {
if (methodCall.getMethodName().equals("doSomething") && methodCall.getClassName().getClassName().equals("the.original.ClassA")) {
methodCall.replace("classB.doSomething($1)");
}
}
}
The only things I noticed was that overloading the method won't work. Let's say ClassB has methods
doSomething(Object)
doSomething(Sting)
doSomething(int)
it will always call the Object method. But thats something i can live with.
If anybody knows a better solution I'd still like to see it.