Javassist-手寫字節碼文件

 
    上篇文章我們學了jdk中動態代理類生成,其實整個過程分兩步,第一步獲取代理類字節碼文件、第二步加載字節碼文件並驗證、解析生成Class對象。在整個過程中發現生成字節碼文件過程是比價麻煩,如果你看過”javaSE虛擬機規範”這本書你也會發現書中大部分也是講.class字節碼文件格式的,所以如果不借助工具自己寫字節碼文件那還是比較難的。
    其實javac編譯器就是一個將我們寫的.java文件轉化爲.class 字節碼文件工具,但是我們沒法使用javac進行動態的字節碼文件編寫,其實除了javac還有很多字節碼編寫工具如jdk.proxy; cglib;javassist;asm;  因在應用中使用javassist的比較多所以學習一下javassist的使用。
 
這篇文章中我們按照下面5個步驟一步步瞭解javassist的使用
1、Javassist動態定義類結構、生成字節碼文件
2、javassist加載已有字節碼文件並進行修改回寫到字節碼文件
3、javassist 代理類生成
4、javassist 中常用類ClassPool、CtClass、CtMethod 等類中的常用方法和注意點
5、javassist 在dubbo中的使用
 
    Javaassist 是一個用來處理 Java 字節碼的類庫。它可以在一個已經編譯好的類中添加新的方法,或者是修改已有的方法,並且不需要對字節碼方面有深入的瞭解。同時也可以去生成一個新的類對象,通過完全手動的方式來書寫字節碼文件。
 
一、生成新類、類字節碼文件生成
 
1、字節碼文件生成類編寫
/**
     * 創建新對象
     *
     * @throws NotFoundException
     * @throws CannotCompileException
     * @throws IOException
     */
    static void addNewClass() throws NotFoundException, CannotCompileException, IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //使用單例模式
        ClassPool classPool = ClassPool.getDefault();
        //創建對象
        CtClass ctClass = classPool.makeClass("com.sb.javassists.CreateObject");

        //添加屬性
        CtField id = new CtField(classPool.get("java.lang.Integer"), "id", ctClass);
        id.setModifiers(Modifier.PRIVATE);
        ctClass.addField(id);

        //添加屬性
        CtField name = new CtField(classPool.get("java.lang.String"), "name", ctClass);
        name.setModifiers(Modifier.PRIVATE);
        ctClass.addField(name);

        //添加屬性(直接寫java代碼)
        CtField desc = CtField.make("private String  desc;", ctClass);
        ctClass.addField(desc);


        //無慘構造函數
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
        ctConstructor.setBody("{}");
        //設置構造函數訪問權限
        ctConstructor.setModifiers(Modifier.PRIVATE);
        ctClass.addConstructor(ctConstructor);
        // 無參構造函數添加
//        ctClass.addConstructor(CtNewConstructor.defaultConstructor(ctClass));

        //2個參數構造函數
        ctConstructor = new CtConstructor(new CtClass[]{classPool.get("java.lang.Integer"), classPool.get("java.lang" +
                ".String")}, ctClass);
        //構造函數函數體
        ctConstructor.setBody("{$0.id=$1;this.name = $2;}");
        ctConstructor.setModifiers(Modifier.PROTECTED);
        ctClass.addConstructor(ctConstructor);

        //3個參構造函數
        ctConstructor = new CtConstructor(new CtClass[]{classPool.get("java.lang.Integer"), classPool.get("java.lang" +
                ".String"), classPool.get("java.lang.String")}, ctClass);
        //構造函數函數體,$0是一個特殊參數即this變量
        ctConstructor.setBody("{$0.id = $1;$0.name = $2;$0.desc = $3;}");
        //默認構造函數爲public
        ctClass.addConstructor(ctConstructor);


        // set get 方法
        ctClass.addMethod(CtNewMethod.setter("setId", id));
        ctClass.addMethod(CtNewMethod.getter("getId", id));

        ctClass.addMethod(CtNewMethod.setter("setName", name));
        ctClass.addMethod(CtNewMethod.getter("getName", name));

        ctClass.addMethod(CtNewMethod.setter("setDesc", desc));
        ctClass.addMethod(CtNewMethod.getter("getDesc", desc));


        //添加方法
        CtMethod printInfo = new CtMethod(CtClass.voidType, "printInfo", new CtClass[]{}, ctClass);
        printInfo.setModifiers(Modifier.PUBLIC);
        printInfo.setBody("{System.out.println(\"id:\"+$0.id +\"  name:\"+this.name+\"  desc:\"+this.desc);}");
        ctClass.addMethod(printInfo);

        //添加方法(直接寫"java代碼")
        CtMethod doDescInfo = CtNewMethod.make("private void  doDescInfo() { System.out.println(\"my " +
                "desc:\"+$0.desc);" +
                " " +
                "}", ctClass);
        ctClass.addMethod(doDescInfo);

        //方法中調用方法
        CtMethod descInfo = CtNewMethod.make("public void descInfo () { $proceed(); }", ctClass, "this", "doDescInfo");
        ctClass.addMethod(descInfo);

        //加載字節碼文件生成Class對象、對象獲取
        Class classObject = ctClass.toClass();
        //構造函數創建對象
        Constructor constructor = classObject.getDeclaredConstructor(new Class[]{Integer.class, String.class});
        Object object = constructor.newInstance(18, "javassist name");

        //反射獲取方法對象
        Method setDescMethod = object.getClass().getMethod("setDesc", String.class);
        setDescMethod.invoke(object, "javassist desc");

        Method printInfoInvoke = classObject.getMethod("printInfo");
        printInfoInvoke.invoke(object, new Object[]{});
        Method descInfoInvoke = classObject.getMethod(("descInfo"), new Class[]{});
        descInfoInvoke.invoke(object, new Object[]{});

        ctClass.writeFile("/Users/wanghaidong/Documents/workspace/projects/ownProjects/inter-sbl/springboot-socks" +
                "/all");
        ctClass.toBytecode();
    }

2、生成字節碼文件反編譯

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sb.javassists;

public class CreateObject {
    private Integer id;
    private String name;
    private String desc;

    private CreateObject() {
    }

    protected CreateObject(Integer var1, String var2) {
        this.id = var1;
        this.name = var2;
    }

    public CreateObject(Integer var1, String var2, String var3) {
        this.id = var1;
        this.name = var2;
        this.desc = var3;
    }

    public void setId(Integer var1) {
        this.id = var1;
    }

    public Integer getId() {
        return this.id;
    }

    public void setName(String var1) {
        this.name = var1;
    }

    public String getName() {
        return this.name;
    }

    public void setDesc(String var1) {
        this.desc = var1;
    }

    public String getDesc() {
        return this.desc;
    }

    public void printInfo() {
        System.out.println("id:" + this.id + "  name:" + this.name + "  desc:" + this.desc);
    }

    private void doDescInfo() {
        System.out.println("my desc:" + this.desc);
    }

    public void descInfo() {
        this.doDescInfo();
        Object var10000 = null;
    }
}

 

二、已有類字節碼文件修改
 
1、已有類
public class UpdateObject {
    public void update() {
        System.out.println("execute update object update method");
    }
}

2、已有類加載並執行

/**
* 加載已指定類
*
* @throws NotFoundException
* @throws CannotCompileException
* @throws IllegalAccessException
* @throws InstantiationException
*/
static void loadClassByPath() throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    ClassPool classPool = ClassPool.getDefault();
    String path = "com.sb.javassists.UpdateObject";
    CtClass ctClass = classPool.get(path);
    Class classObject = ctClass.toClass();
    Object object = classObject.newInstance();
    Method method = classObject.getDeclaredMethod("update");
    method.invoke(object, new Object[]{});

}

3、加載原有字節碼文件並一次修改多行

(1)、修改類字節碼文件代碼(一次添加多行,單行的沒有什麼特殊的,網上也很多所以不寫了……)
/**
* 多行代碼添加使用{}
*
* @throws NotFoundException
* @throws CannotCompileException
*/
static void updateMethodMore() throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
    ClassPool classPool = ClassPool.getDefault();
    String path = "com.sb.javassists.UpdateObject";
    CtClass ctClass = classPool.get(path);
    CtMethod ctMethod = ctClass.getDeclaredMethod("update");
    //注意這樣添加的每一行代碼其作用範圍都是在本代碼快而不是方法,所以如果在代碼塊之外引用則會報未定義變量異常
    ctMethod.insertBefore("long begin= System.currentTimeMillis();");
    ctMethod.insertAfter("{long end = System.currentTimeMillis();System.out.println(end);}");
    Class classObject = ctClass.toClass();
    Object object = classObject.newInstance();
    Method method = classObject.getDeclaredMethod("update");
    method.invoke(object, new Object[]{});
    //將修改後的字節碼寫入文件生成.class 文件
    ctClass.writeFile("/Users/wanghaidong/Documents/workspace/projects/ownProjects/inter-sbl/springboot-socks" +
            "/all");
}

(2)、修改後.class 文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sb.javassists;

public class UpdateObject {
    public UpdateObject() {
    }

    public void update() {
        long var1 = System.currentTimeMillis();
        System.out.println("execute update object update method");
        Object var4 = null;
        long var5 = System.currentTimeMillis();
        System.out.println(var5);
    }
}

5、加載原有字節碼文件並添加異常捕獲

(1)、修改類字節碼文件代碼(添加異常)
/**
     * 方法添加異常try(){}catch(){}
     * 注意添加異常塊時一定要添加throw $e即一定要拋出異常,否則編譯錯誤;$e:標識異常值
     * javassist.CannotCompileException: by javassist.bytecode.BadBytecode: no basic block at 9
     *
     * @throws NotFoundException
     * @throws CannotCompileException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    static void addTryCatch() throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException, IOException, NoSuchMethodException, InvocationTargetException {
        ClassPool classPool = ClassPool.getDefault();
        String path = "com.sb.javassists.UpdateObject";
        CtClass ctClass = classPool.get(path);
        CtMethod ctMethod = ctClass.getDeclaredMethod("update");
        //異常Exception類引用
        CtClass exception = ClassPool.getDefault().get("java.lang.Exception");
        //添加異常捕獲;打印異常並再次拋出異常,$e代表異常值
        ctMethod.addCatch("{ System.out.println($e); throw $e; }", exception);
        //這種寫法是可以的
//        ctMethod.addCatch("{throw $e; }", exception);
        //這種寫法報異常,編譯無法通過
//        ctMethod.addCatch("{System.out.println($e);}", exception);

        Class classObject = ctClass.toClass();
        Object object = classObject.newInstance();
        Method method = classObject.getDeclaredMethod("update");
        method.invoke(object, new Object[]{});

        ctClass.writeFile("/Users/wanghaidong/Documents/workspace/projects/ownProjects/inter-sbl/springboot-socks" +
                "/all");
    }

(2)、修改後.class 文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sb.javassists;

public class UpdateObject {
    public UpdateObject() {
    }

    public void update() {
        try {
            System.out.println("execute update object update method");
        } catch (Exception var2) {
            System.out.println(var2);
            throw var2;
        }
    }
}

6、加載原有字節碼文件並添加新方法、新方法調用原有方法、間接實現方法體修改

(1)、修改類字節碼文件代碼(添加try{}catch(Exception e){}finally{})
/**
* 修改方法
*
* @throws NotFoundException
* @throws CannotCompileException
* @throws IOException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws NoSuchMethodException
* @throws InvocationTargetException
*/
static void addTryCatchFinally() throws NotFoundException, CannotCompileException, IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    ClassPool classPool = ClassPool.getDefault();
    String path = "com.sb.javassists.UpdateObject";

    CtClass ctClass = classPool.get(path);
    String methodName = "update";
    String methodNameNew = "doUpdate";
    CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
    //修改原方法名稱()
    ctMethod.setName(methodNameNew);
    //原方法拷貝,只拷貝了方法結構即方法返回類型、形參,未拷貝方法名稱、方法體;
    CtMethod newCtMethod = CtNewMethod.copy(ctMethod, methodName, ctClass, null);

    //方法體添加
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append("{try{");
    //調用原有方法,($$)標識方法所有參數,相當與this.doUpdate($1,$2,$3……)
    stringBuffer.append(methodNameNew).append("($$);");

    //爲了測試在這裏添加異常
    stringBuffer.append("throw new Exception(\"test exception\");");
    //這裏使用手寫添加異常所以$e在這裏無法識別
    stringBuffer.append("}catch(Exception e){System.out.println(\"異常信息:\"+e.getMessage" +
            "());}");
    stringBuffer.append("finally{System.out.println(\"add tyr catch finally end \");}");
    //上面這些都是一個方法塊,所以不要丟了開始和結束時的{}
    stringBuffer.append("}");
    // 新方法設置方法體
    newCtMethod.setBody(stringBuffer.toString());
    //添加新方法
    ctClass.addMethod(newCtMethod);

    Class classObject = ctClass.toClass();
    Object object = classObject.newInstance();
    Method method = classObject.getDeclaredMethod("update");
    method.invoke(object, new Object[]{});

    ctClass.writeFile("/Users/wanghaidong/Documents/workspace/projects/ownProjects/inter-sbl/springboot-socks" +
            "/all");
}

(2)、修改後.class文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sb.javassists;

public class UpdateObject {
    public UpdateObject() {
    }

    public void doUpdate() {
        System.out.println("execute update object update method");
    }

    public void update() {
        try {
            this.doUpdate();
            throw new Exception("test exception");
        } catch (Exception var4) {
            System.out.println("異常信息:" + var4.getMessage());
        } finally {
            System.out.println("add tyr catch finally end ");
        }

    }
}

7、加載原有字節碼文件並添加鏈路id值

(1)、方法開始添加traceId、結尾刪除traceId
 
/**
* 添加TraceId,寫全路勁
*
* @throws NotFoundException
* @throws CannotCompileException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws IOException
* @throws NoSuchMethodException
* @throws InvocationTargetException
*/
static void addTraceId() throws NotFoundException, CannotCompileException,
        IllegalAccessException, InstantiationException, IOException, NoSuchMethodException, InvocationTargetException {
    ClassPool classPool = ClassPool.getDefault();
    String path = "com.sb.javassists.UpdateObject";
    CtClass ctClass = classPool.get(path);
    String methodName = "update";
    String methodNameNew = "doUpdate";
    CtMethod ctMethod = ctClass.getDeclaredMethod("update");
    //修改原方法名稱
    ctMethod.setName(methodNameNew);

    //定義新方法,拷貝舊方法方法結構體、方法返回值、形參,未拷貝方法名稱、方法體;
    CtMethod newCtMethod = CtNewMethod.copy(ctMethod, methodName, ctClass, null);

    //新方法方法體
    StringBuffer bodyBuffer = new StringBuffer();
    //設置traceId 值
    bodyBuffer.append("{org.slf4j.MDC.put(\"traceId\",\"1\");try{");
    // 調用原有代碼,類似於method();($$)表示所有的參數
    // $$是所有方法參數的簡寫,主要用在方法調用上。例如:
    // 原方法 move(String a,String b)
    // move($$) 相當於move($1,$2)
    //
    bodyBuffer.append(methodNameNew).append("($$);");
    // 獲取traceId的值
    bodyBuffer.append("String traceId= org.slf4j.MDC.get(\"traceId\");System.out.println(traceId);");
    bodyBuffer.append("throw new Exception(\"test exception\");");
    bodyBuffer.append("}catch(Exception e){System.out.println(e.getMessage());}");
    bodyBuffer.append("finally{org.slf4j.MDC.remove(\"traceId\");System.out.println(\"ok!\");}");
    bodyBuffer.append("}");
    // 新方法體設置
    newCtMethod.setBody(bodyBuffer.toString());
    //添加新方法
    ctClass.addMethod(newCtMethod);

    Class classObject = ctClass.toClass();
    Object object = classObject.newInstance();
    Method method = classObject.getDeclaredMethod("update");
    method.invoke(object, new Object[]{});

    ctClass.writeFile("/Users/wanghaidong/Documents/workspace/projects/ownProjects/inter-sbl/springboot-socks" +
            "/all");
}

(2)、修改後.class 文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sb.javassists;

import org.slf4j.MDC;

public class UpdateObject {
    public UpdateObject() {
    }

    public void doUpdate() {
        System.out.println("execute update object update method");
    }

    public void update() {
        MDC.put("traceId", "1");

        try {
            this.doUpdate();
            String var1 = MDC.get("traceId");
            System.out.println(var1);
            throw new Exception("test exception");
        } catch (Exception var5) {
            System.out.println(var5.getMessage());
        } finally {
            MDC.remove("traceId");
            System.out.println("ok!");
        }

    }
}

8、加載原有字節碼文件並添加鏈路id值

 上面7中沒有使用classPool.importPackage()方法引入jar包所以,每次都是寫類全名比較麻煩,所以在開始可以直接引入jar包這樣就不用寫類全路徑,寫起來比較方便。
classPool.importPackage(“org.slf4j”)
 
三、動態代理類生成
 
1、使用javassist ProxyFactory 生成代理類
 
(1)、委託類
public class Student implements People {

    @Override
    public void say() {
        System.out.println("HELLO JAVA HELLO DYNAMIC PROXY");
    }

    @Override
    public void eat() {
        System.out.println("input byte  return Class object");
    }

}

(2)、代理類生成

public Object getProxy(Class<?> type) throws IllegalAccessException, InstantiationException {
    //代理工程
    ProxyFactory f = new ProxyFactory();
    //設置生成代理類的類,這裏注意這裏的type是類不是接口的Class對象
    f.setSuperclass(type);
    //創建指定類的代理類類對象
    Class c = f.createClass();
    //方法處理類,功能類似jdk動態代理中的InvocationHandler實現類
    MyMethodHandler mi = new MyMethodHandler();
    //創建代理類
    Object proxy = c.newInstance();
    //代理類設置處理類handler(構造函數注入、方法注入)
    ((Proxy) proxy).setHandler(mi);
    return proxy;
}

(3)、代理關係

/**
* 處理類,這個類類似於jdk的InvocationHandler、起到代理類、被代理類關聯關係
*/
private static class MyMethodHandler implements MethodHandler {

    /**
     * @param target       目標類
     * @param method       代理類調用方法
     * @param method1      動態生成的方法
     * @param methodParams 代理類調用方法參數
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object target, Method method, Method method1, Object[] methodParams) throws Throwable {
        for (Object object : methodParams) {
            System.out.println(object.getClass().getName());
        }
        System.out.println("method name =" + method.getName());
        System.out.println("method1 name =" + method1.getName());
        System.out.println("method execute before");
        Object res = method1.invoke(target, methodParams);
        System.out.println("method execute after");
        return res;
    }
}

(4)、執行結果

method name =say
method1 name =_d8say
method execute before
HELLO JAVA HELLO DYNAMIC PROXY
method execute after

2、使用javassist bytecode 生成代理類

 
(1)、委託類
public class Student implements People {

    @Override
    public void say() {
        System.out.println("HELLO JAVA HELLO DYNAMIC PROXY");
    }

    @Override
    public void eat() {
        System.out.println("input byte  return Class object");
    }

}

(2)、調用類

// 使用javassist 來動態生成代理類
Student student = new Student();
JavassistProxyFactory javassistProxyFactory = new JavassistProxyFactory();
People proxy = javassistProxyFactory.getProxy(Student.class, new MyInvocationHandler(student));
proxy.say();

(3)、生成代理類工廠

import java.lang.reflect.InvocationHandler;

public class JavassistProxyFactory implements ProxyFactory {

    @Override
    public <T> T getProxy(Class<T> target, InvocationHandler handler) throws Throwable {
        return (T) ProxyGenerator.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target, handler);
    }
}

(4)、工廠接口

import java.lang.reflect.InvocationHandler;

public interface ProxyFactory {
    /**
     * 泛型方法
     *
     * @param target
     * @param handler
     * @param <T>
     * @return
     * @throws Throwable
     */
    <T> T getProxy(Class<T> target, InvocationHandler handler) throws Throwable;
}

(5)、使用javassist byteCode 生成代理類


import javassist.*;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/***
* @author wanghaidong
* @data 2019/8/23
*/

public class ProxyGenerator {
    private static final AtomicInteger counter = new AtomicInteger(1);

    /**
     * 緩存
     */
    private static final ConcurrentHashMap<Class<?>, Object> proxyInstanceCache = new ConcurrentHashMap<>();

    /**
     * 代理類對象生成
     *
     * @param classLoader       類加載器
     * @param targetClass       被代理類(委託類),這個參數可以按照自己需求傳遞兩種參數
     *                          第一種:被代理類,通過他可以獲取代理類名稱、接口方法;第二種:直接傳接口class對象集合
     * @param invocationHandler 調用處理器,做爲代理關係存在
     * @return
     * @throws Exception
     */
    public static Object newProxyInstance(ClassLoader classLoader, Class<?> targetClass, InvocationHandler invocationHandler)
            throws Exception {
        //判斷是否在緩存中
        if (proxyInstanceCache.containsKey(targetClass)) {
            return proxyInstanceCache.get(targetClass);
        }
        //生成類容器
        ClassPool pool = ClassPool.getDefault();

        //生成代理類的全限定名
        String qualifiedName = generateClassName(targetClass);
        CtClass proxy = pool.makeClass(qualifiedName);

        //添加屬性,接口方法列表
        CtField mf = CtField.make("public static java.lang.reflect.Method[] methods;", proxy);
        proxy.addField(mf);
        //添加InvocationHandler變量
        CtField hf = CtField.make("private " + InvocationHandler.class.getName() + " handler;", proxy);
        proxy.addField(hf);
        //生成構造函數,參數爲InvocationHandler 類型並給定義的handler 變量賦值
        CtConstructor constructor = new CtConstructor(new CtClass[]{pool.get(InvocationHandler.class.getName())}, proxy);
        constructor.setBody("this.handler=$1;");
        constructor.setModifiers(Modifier.PUBLIC);
        proxy.addConstructor(constructor);

        //無參構造函數添加
        proxy.addConstructor(CtNewConstructor.defaultConstructor(proxy));

        //獲取被代理類的所有接口類類型對象
        Class<?>[] classes = targetClass.getInterfaces();
        List<Class<?>> interfaces = Arrays.asList(classes);

        //遍歷所有接口
        List<Method> methods = new ArrayList<>();
        for (Class cls : interfaces) {
            CtClass ctClass = pool.get(cls.getName());
            //給代理類添加實現接口
            proxy.addInterface(ctClass);
            //獲取接口所有方法
            Method[] arr = cls.getDeclaredMethods();
            //遍歷方法並
            for (Method method : arr) {
                int ix = methods.size();
                //方法返回值類型
                Class<?> rt = method.getReturnType();
                //方法參數類型
                Class<?>[] pts = method.getParameterTypes();

                StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
                for (int j = 0; j < pts.length; j++) {
                    code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
                }
                code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");
                if (!Void.TYPE.equals(rt)) {
                    code.append(" return ").append(asArgument(rt, "ret")).append(";");
                }

                StringBuilder sb = new StringBuilder(1024);
                sb.append(modifier(method.getModifiers())).append(' ').append(getParameterType(rt)).append(' ').append(method.getName());
                sb.append('(');
                for (int i = 0; i < pts.length; i++) {
                    if (i > 0) {
                        sb.append(',');
                    }
                    sb.append(getParameterType(pts[i]));
                    sb.append(" arg").append(i);
                }
                sb.append(')');
                //方法拋出異常
                Class<?>[] ets = method.getExceptionTypes();
                if (ets != null && ets.length > 0) {
                    sb.append(" throws ");
                    for (int i = 0; i < ets.length; i++) {
                        if (i > 0) {
                            sb.append(',');
                        }
                        sb.append(getParameterType(ets[i]));
                    }
                }
                sb.append('{').append(code.toString()).append('}');

                CtMethod ctMethod = CtMethod.make(sb.toString(), proxy);
                proxy.addMethod(ctMethod);

                methods.add(method);
            }
        }
        //代理類訪問權限設置
        proxy.setModifiers(Modifier.PUBLIC);
        //加載字節碼文件生成Class對象、對象獲取
        Class<?> proxyClass = proxy.toClass();
        proxyClass.getField("methods").set(null, methods.toArray(new Method[0]));

        //獲取構造函數
        Object instance = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
        Object old = proxyInstanceCache.putIfAbsent(targetClass, instance);
        if (old != null) {
            instance = old;
        }
        //字節碼文件保存
        proxy.writeFile("/Users/wanghaidong/Documents/workspace/projects/ownProjects/inter-sbl/springboot-socks" +
                "/all");
        return instance;
    }

    /**
     * 訪問權限獲取
     *
     * @param mod
     * @return
     */
    private static String modifier(int mod) {
        if (Modifier.isPublic(mod)) {
            return "public";
        }
        if (Modifier.isProtected(mod)) {
            return "protected";
        }
        if (Modifier.isPrivate(mod)) {
            return "private";
        }
        return "";
    }

    /**
     * 數組類型返回 String[]
     *
     * @param c
     * @return
     */
    public static String getParameterType(Class<?> c) {
        //數組類型
        if (c.isArray()) {
            StringBuilder sb = new StringBuilder();
            do {
                sb.append("[]");
                c = c.getComponentType();
            } while (c.isArray());

            return c.getName() + sb.toString();
        }
        return c.getName();
    }

    /**
     * 參數類型判斷
     *
     * @param cl
     * @param name
     * @return
     */
    private static String asArgument(Class<?> cl, String name) {
        if (cl.isPrimitive()) {
            if (Boolean.TYPE == cl) {
                return name + "==null?false:((Boolean)" + name + ").booleanValue()";
            }
            if (Byte.TYPE == cl) {
                return name + "==null?(byte)0:((Byte)" + name + ").byteValue()";
            }
            if (Character.TYPE == cl) {
                return name + "==null?(char)0:((Character)" + name + ").charValue()";
            }
            if (Double.TYPE == cl) {
                return name + "==null?(double)0:((Double)" + name + ").doubleValue()";
            }
            if (Float.TYPE == cl) {
                return name + "==null?(float)0:((Float)" + name + ").floatValue()";
            }
            if (Integer.TYPE == cl) {
                return name + "==null?(int)0:((Integer)" + name + ").intValue()";
            }
            if (Long.TYPE == cl) {
                return name + "==null?(long)0:((Long)" + name + ").longValue()";
            }
            if (Short.TYPE == cl) {
                return name + "==null?(short)0:((Short)" + name + ").shortValue()";
            }
            throw new RuntimeException(name + " is unknown primitive type.");
        }
        return "(" + getParameterType(cl) + ")" + name;
    }

    /**
     * 生成代理類名稱
     *
     * @param type
     * @return
     */
    private static String generateClassName(Class<?> type) {

        return String.format("%s$Proxy%d", type.getName(), counter.getAndIncrement());
    }
}

(6)、執行結果

調用委託類方法前置處理
HELLO JAVA HELLO DYNAMIC PROXY
調用委託類方法後置處理
 
(7)、動態生成字節碼文件

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class Student$Proxy1 implements People {
    public static Method[] methods;
    private InvocationHandler handler;

    public Student$Proxy1(InvocationHandler var1) {
        this.handler = var1;
    }

    public Student$Proxy1() {
    }

    public void eat() {
        Object[] var1 = new Object[0];
        this.handler.invoke(this, methods[0], var1);
    }

    public void say() {
        Object[] var1 = new Object[0];
        this.handler.invoke(this, methods[1], var1);
    }
}

     整個過程使用javassist 按照委託類實現接口獲取接口方法來動態生成代理類字節碼文件,並加載該字節碼文件生成Class對象……

    因爲使用javassist 可以動態的編寫字節碼文件,所以如果你瞭解生成的動態代理類,則你可以直接編寫代理類,然後生成字節碼文件、在通過驗證、解析來生成Class對象。
 
四、編寫字節文件注意點
1、無慘構造函數還是要寫構造體,如果不寫構造函數體則會報異常,異常信息如下
Caused by: java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file com/sb/javassists/CreateObject
 
解決方式:無慘構造函數添加函數體  ctConstructor.setBody("{}");
 
 
2、無慘構造函數添加
ctClass.addConstructor(CtNewConstructor.defaultConstructor(ctClass));
這種方式解決了上面無參構造函數沒有函數體報異常的問題。但弊端是應該沒法設置訪問權限
 
3、CtClass 三個方法:toBytecode()、toClass()、writeFile()
 
(1)、當調用CtClass()中的writeFile()時,此更改將反映在原始類文件中;CtClass對象轉換爲類文件並將其寫入本地磁盤
(2)、toClass()請求當前的上下文加載器加載CtClass代表的類文件。它返回一個代表被加載的類的java.lang.Class對象
(3)、要獲取字節碼,使用toBytecode()
 
4、CtClass 類凍結
如果通過writeFile(),toClass()或toBytecode()將CtClass對象轉換爲類文件,Javassist將凍結該CtClass`對象。不允許對該CtClass對象進行進一步修改。這是爲了在開發人員嘗試修改已加載的類文件時警告開發人員,因爲JVM不允許重新加載類。
 
5、類解凍
凍結的CtClass可以解凍,以便允許修改類定義
  CtClasss cc = ...;
      :
  cc.writeFile();
  cc.defrost();
  cc.setSuperclass(...);    // OK since the class is not frozen.
 
6、特殊情況下不能解凍
如果ClassPool.doPruning設置爲true,那麼當Javassist凍結該對象時,Javassist會修剪CtClass對象中包含的數據結構。 爲了減少內存消耗,修剪會丟棄該對象中不必要的屬性(attribute_info結構)。例如,丟棄Code_attribute結構(方法體)。 因此,在修剪CtClass對象之後,除方法名稱,簽名和註釋外,不能訪問方法的字節碼。 修剪過的CtClass對象無法再次解凍。 ClassPool.doPruning的默認值爲false。
 
7、從ClassPool中釋放CtClass、避免內存溢出detach()方法
ClassPool對象是CtClass對象的容器。 創建CtClass對象後,它將永遠記錄在ClassPool中。 這是因爲編譯器在編譯引用該CtClass表示的類的源代碼時可能需要稍後訪問CtClass對象;
 
(1)、如果CtClass對象的數量變得非常大,那麼ClassPool的這種規範可能會導致巨大的內存消耗(這很少發生,因爲Javassist試圖以各種方式減少內存消耗)。 要避免此問題,可以從ClassPool中顯式刪除不必要的CtClass對象。 如果在CtClass對象上調用detach(),則會從ClassPool中刪除該CtClass對象
 
(2)、調用detach()後,不得在該CtClass對象上調用任何方法。 但是,您可以在ClassPool上調用get()以使CtClass的新實例表示相同的類。 如果調用get(),ClassPool會再次讀取一個類文件並重新創建一個CtClass對象,該對象由get()返回。
 
8、方法參數
$0, $1, $2, ...    this and actual parameters
$$    All actual parameters.For example, m($$) is equivalent to m($1,$2,...)
$0標識符表示this、$1標識符表示方法第一個參數、$2標識符標識方法第二個參數……  $$ 標識方法所有參數。
也看到了在上面的構造函數中有使用$0的也有使用this的,所有$0 不是方法的第一個參數而是this的標識。
 
9、方法中添加代碼,如果是多行使用”{}” 括號括起來
 
10、如果使用 addCatch 來給方法添加異常捕獲則必須要throw $e,其中$e標識異常;否則會報如下錯誤
javassist.CannotCompileException: by javassist.bytecode.BadBytecode: no basic block at 9
 
11、classPool.importPackage("org.slf4j);引入jar包
 
五、javassist 在dubbo中的使用
1、JavassistProxyFactory 代理類工廠
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

這裏有個奇怪的問題,Proxy類中的newInstance 是一個抽象方法,那他是在哪裏實現的,如果第一次看或比較粗心你可能也和我一樣在想他到底是在哪實現的。後來發現就在眼前(哎遠在天邊近在眼前……),看下面代理類生成代碼塊,是不是想弄死自己的心都有……

2、Proxy類
(1)、代理類生成
 
// create ProxyInstance class.
String pcn = pkg + ".proxy" + id;
ccp.setClassName(pcn);
ccp.addField("public static java.lang.reflect.Method[] methods;");
ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");
ccp.addDefaultConstructor();
Class<?> clazz = ccp.toClass();
clazz.getField("methods").set(null, methods.toArray(new Method[0]));

(2)、Proxy類對象動態生成

// create Proxy class.
String fcn = Proxy.class.getName() + id;
ccm = ClassGenerator.newInstance(cl);
ccm.setClassName(fcn);
ccm.addDefaultConstructor();
ccm.setSuperClass(Proxy.class);
ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
Class<?> pc = ccm.toClass();
proxy = (Proxy) pc.newInstance();

這裏使用javassist 來動態編寫Proxy 這個類的字節碼文件並加載生成Class對象,之後返回Proxy實例對象。

ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
看到這一行沒,就是JavassistProxyFactory 中納悶半天,結果在這裏使用動態編寫字節碼文件方式實現了,newInstance()方法返回了代理類實例對象。
 
注意:這裏的Proxy類不是java中的Proxy類而是dubbo自定義的一個抽象類。
 
(3)、執行完(1)、(2)之後已經創建了動態代理類和Proxy對象實例
Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); 這個方法返回的就是生成的代理類,參數new InvokerInvocationHandler(invoker) 就是代理類構造函數參數。
 
(4)、生成的代理類字節碼文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.dubbo.common.bytecode;

import java.lang.reflect.InvocationHandler;
import org.apache.dubbo.common.bytecode.ClassGenerator.DC;

public class Proxy0 extends Proxy implements DC {
    public Object newInstance(InvocationHandler var1) {
        return new proxy0(var1);
    }

    public Proxy0() {
    }
}

dubbo中代理類的獲取都是調用的ClassGenerator 類方法,但是ClassGenerator中封裝了javassist的ClassPool、CtClass、CtMethod。而具體的代理類獲取操作是在Proxy類中進行,如果對具體生成代理類感興趣可以看看Proxy、ClassGenerator兩個類。

 
六、javassist文檔
 
七、性能對比
JDK自帶的,ASM,CGLIB(基於ASM包裝),JAVAASSIST,
使用的版本分別爲:
JDK-1.6.0_18-b07, ASM-3.3, CGLIB-2.2, JAVAASSIST-3.11.0.GA
 
 
八、Maven依賴
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-GA</version>
    <scope>compile</scope>
    <optional>true</optional>
</dependency>
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章