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