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

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