javasisit对class字节码基本使用以及对一个class多次修改

需求:(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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章