javasisit对class字节码基本使用以及对一个class多次修改
程序员文章站
2022-06-18 11:09:34
...
需求:(1)对UserServiceImpl现有字节码进行修改逻辑操作
(2)修改完成后使用该class
(3) 再次修改UserServiceImpl字节码,添加新逻辑
(4)修改完成后使用该class
遇到的问题: (1)一个类只能被加载一次
(2)类加载后默认不能为修改
(3)不同classload 下的同名类不能强行转化
javassist 简介
Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简
单,⽽且快速。直接使⽤java编码的形式,⽽不需要了解虚拟机指令,就能动态改变类的结
构,或者动态⽣成
原始类文件
javassist API介绍
API执⾏流程
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