欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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执⾏流程

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

 

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

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

相关标签: java javassist