how to change field‘s initial value using javassist
java, javassist
Solution
For question #1, yes, you can achieve this via modify `ApiFacadeImpl`'s constructor. Remember to use `CtConstructor#insertAfter` to append the assignment statement.
ClassPool pool = ClassPool.getDefault();
CtClass factoryClass = pool.getCtClass("ServiceFactory");
CtConstructor constructor = factoryClass.getDeclaredConstructor(null);
String setMockStatement = String.format("service = new %s();",
MockServiceImpl.class.getCanonicalName());
constructor.insertAfter(setMockStatement);
factoryClass.toClass();
new ServiceFactory().getService().say();
I tried the way you proposed. However, it didn't work. After some debuggings, I found that if we remove a field, it won't remove the initialization statement for this field. If we add the field again with our expected initialization statement, it will executed before the original initialization statement. So the `service` field is assigned to `MockServiceImpl` at first and then assigned to `null`. Please refer to the following javassist bug.
javassist-3.14.0-GA - Problm in default initializer of class variables when they are deleted and created using removeField and addField, CtField.make
For question #2, I am not sure why this happens. What is your javassist version? How does your mockClass look like? Could you please refer to the following javassist bug?
Javassist causes java.lang.ClassFormatError: Invalid length 561 in LocalVariableTable in class file
Problem
I'm trying to mock some services in my developing env. The serviceFactory code is something like: ``` public class ApiFacadeImpl implements ApiFacade { private OneService oneService = null; public OneService getOneService(){ if(oneService==null) {//some initialization steps } return oneService; } } ``` I know this factory is not well coded and designed. However I can't modify its code. my idea is to change the bytecode, so I can redefine it to something like: ``` public class ApiFacadeImpl implements ApiFacade { private OneService oneService = new MyMockOneService(); .... } ``` My 1st is: is this possible using javassist? And how? Since I can't find anything like reinitialize a field using javassist using google, I tried myself by deleting it and recreate it: ``` CtField oneServiceField = cc.getDeclaredField("oneService"); cc.removeField(oneServiceField); CtField f = CtField.make(String.format("private %s %s=new %s();", oneServiceField.getType().getName(), "oneService", mockClass.getCanonicalName()), cc); cc.addField(f); cc.toClass(); ``` then I got exception: ``` javassist.CannotCompileException: by java.lang.ClassFormatError: Invalid length 99 in LocalVariableTable in class file com/Test at javassist.ClassPool.toClass(ClassPool.java:1051) at javassist.ClassPool.toClass(ClassPool.java:994) at javassist.ClassPool.toClass(ClassPool.java:952) at javassist.CtClass.toClass(CtClass.java:1079) ``` My 2nd question is why this exception? Which step disobey java class's definition? And when I delete the field does javassist help to delete field reference in: ``` public OneService getOneService(){ if(oneService==null) {//some initialization steps } return oneService; } ``` Many thanks.