需求:(1)對UserServiceImpl現有字節碼進行修改邏輯操作
(2)修改完成後使用該class
(3) 再次修改UserServiceImpl字節碼,添加新邏輯
(4)修改完成後使用該class
遇到的問題: (1)一個類只能被加載一次
(2)類加載後默認不能爲修改
(3)不同classload 下的同名類不能強行轉化
原始類文件
public class UserServiceImpl implements UserService{
public void getUser() {
System.out.println("raw getUser method");
}
public void addUser(String name, String sex) {
System.out.println("raw addUser method : " + name + " - " + sex);
}
public void addUser2(String name, String sex) {
System.out.println("raw addUser2 method : " + name + " - " + sex);
}
}
public interface UserService {
public void getUser();
public void addUser(String name, String sex);
public void addUser2(String name, String sex);
}
新classloader簡單實現
public class NewClassLoader extends ClassLoader {
private byte[] codeByte;
public void setCodeByte(byte[] codeByte) {
this.codeByte = codeByte;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> cls = findLoadedClass(name);
if (cls != null) {
return cls;
}
try {
byte[] bytes = codeByte;
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.loadClass(name);
}
}
測試方法
@Test
public void updateMethod() throws NotFoundException, CannotCompileException, IOException {
ClassPool pool = new ClassPool();
pool.appendSystemPath();
CtClass ctl = pool.get("com.meng.exapmle.agent.UserServiceImpl");
CtField f = new CtField(pool.get(String.class.getName()), "abc", ctl);
ctl.addField(f);
CtMethod mehod = ctl.getDeclaredMethod("getUser");
mehod.insertBefore("System.out.println(\"abc :\" + abc);");
CtMethod mehod2 = ctl.getDeclaredMethod("addUser");
mehod2.insertBefore("abc = $1;");
ctl.toClass();
File file = new File(System.getProperty("user.dir") + "/target/UserServiceImpl.class");
file.createNewFile();
Files.write(file.toPath(), ctl.toBytecode());
com.meng.exapmle.agent.UserServiceImpl userService = new com.meng.exapmle.agent.UserServiceImpl();
userService.addUser("meng1", "man");
userService.getUser();
//嘗試再次修改
//如果一個CtClass對象通過writeFile(),toClass()或者toByteCode()轉換成class文件,
// 那麼javassist會凍結這個CtClass對象。後面就不能修改這個CtClass對象了。
// 這樣是爲了警告開發者不要修改已經被JVM加載的class文件,因爲JVM不允許重新加載一個類。
System.out.println("-------------");
//若想對CtClass對象進行修改,必須對其進行解凍,通過defrost()方法進行
if(ctl.isFrozen()){
ctl.defrost();
}
//再次修改方法
mehod = ctl.getDeclaredMethod("getUser");
mehod.insertBefore("System.out.println(\"abc :\" + abc);");
System.out.println("---------");
//同一個ClassLoader不能多次加載同一個類。 如果重複的加載同一個類 ,
// 將會拋出 attempted duplicate class definition for name: "com/meng/exapmle/agent/UserServiceImpl 異常。
// 所以,在替換Class的時候, 加載該Class的ClassLoader也必須用新的。
//使用新的ClassLoader
try {
NewClassLoader localClassLoader = new NewClassLoader();
// file = new File(System.getProperty("user.dir") + "/target/UserServiceImpl.class");
// if(file.exists()){
// file.delete();
// }
// file.createNewFile();
// Files.write(file.toPath(), ctl.toBytecode());
// localClassLoader.setCodeByte(IOUtils.toByteArray(new FileInputStream(file)));
localClassLoader.setCodeByte(ctl.toBytecode());
Class serviceClass = localClassLoader.findClass(ctl.getName());
//此處必須使用接口否則轉化失敗是因爲classloader不用
//java.lang.ClassCastException: com.meng.exapmle.agent.UserServiceImpl cannot be cast to com.meng.exapmle.agent.UserServiceImpl
UserService userService1 = (UserService) serviceClass.newInstance();
userService1.addUser("haha", "oo");
userService1.getUser();
System.out.println("----------");
//反射調用
Object userService2 = serviceClass.newInstance();
Method method = serviceClass.getMethod("addUser", String.class, String.class);
method.invoke(userService2,"meng1", "man");
method = serviceClass.getMethod("getUser");
method.invoke(userService2);
} catch (ClassNotFoundException e) {
System.out.println(e.getMessage());
} catch (IllegalAccessException e) {
System.out.println(e.getMessage());
} catch (InstantiationException e) {
System.out.println(e.getMessage());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
代碼地址:https://github.com/291850336asd/LearnTest/tree/master/agent/src/test/java/com/meng/exapmle