使用ASM實現動態代理

若是對我這段代碼感興趣,直接拷貝測試debug,要否則你不知道我寫的是什麼鬼,若是有什麼問題,能夠告訴我。java

1、實現動態代理,首先得考慮有應該定義哪些類,根據JDK的動態代理思想,那麼它就應該有一個生成代理的類web

package com.asm_core;

import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.List;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.util.TraceClassVisitor;

import com.asm_core.logic.AddLogic;

/**
 * 生成代理對象
 * @author may
 *
 */
public class GenSubProxy {
    
    //邏輯接口
    private AddLogic logic = null;
    //被代理類的全部方法
    private Method[] methods = null;
    
    private String classNamePrefix = null;
    
    private String descInfoPrefix = null;
    
    private String logicPkg = null;
    
    public GenSubProxy(AddLogic logic) {
        
        String logicClassName = AddLogic.class.getName();
        
        this.logicPkg = logicClassName.substring(0, logicClassName.lastIndexOf(AddLogic.class.getSimpleName())).replace(".", "/");
                
        this.logic = logic;
        
    }
    
    
    public Object genSubProxy(Class<?> superClass) {
        
        //得到被代理類的方法集合
        methods = superClass.getDeclaredMethods();
        
        classNamePrefix = superClass.getName().substring(0, superClass.getName().lastIndexOf(superClass.getSimpleName()));
        
        descInfoPrefix = classNamePrefix.replace(".", "/");
        
        Object obj = null;
        try {
            PrintWriter pw = new PrintWriter(System.out, true);
            //生成ClassNode
            ClassNode cn = genClassNode(superClass);
            //ClassWriter.COMPUTE_FRAMES表示讓asm自動生成棧圖,雖然會慢上二倍。
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            
            TraceClassVisitor tcv = new TraceClassVisitor(cw, pw);
            
            cn.accept(tcv);
            
            //cn.accept(cw);
            
            byte[] b = cw.toByteArray();
            
            MyClassLoader classLoader = new MyClassLoader(b);
            
            Class<?> proxy = classLoader.loadClass(classNamePrefix + "$Proxy");
            
            obj = proxy.newInstance();
            
            Method method = proxy.getDeclaredMethod("setLogic", AddLogic.class);
            
            method.invoke(obj, logic);
            
            Method meth = proxy.getDeclaredMethod("setMethods", Method[].class);
             
            meth.invoke(obj, new Object[] {methods});
             
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return obj;
    }
    
    public ClassNode genClassNode(Class<?> superClass) {
        
        String superName = superClass.getName().replace(".", "/");
        
        ClassNode cn = new ClassNode(Opcodes.ASM5);
        
    
        //定義代理類的類名
        cn.name = descInfoPrefix + "$Proxy";
        //超類爲當前被代理類
        cn.superName = superName;
        
        cn.access = Opcodes.ACC_PUBLIC;
        
        cn.version = Opcodes.V1_8;
        
        cn = proxyMethod(cn);
        
    
        
        
        return cn;
        
    }
    
    @SuppressWarnings("all")
    public ClassNode proxyMethod(ClassNode cn) {
        
        List<MethodNode> list = cn.methods;
        
        List<FieldNode> fields = cn.fields;
        //這裏賦初始值必須是Integer, Float, Long, Double 或者 String,null
        fields.add(new FieldNode(Opcodes.ACC_PUBLIC, "logic", "L" + logicPkg + "AddLogic;", null, null));
        //添加methods字段
        fields.add(new FieldNode(Opcodes.ACC_PUBLIC, "methods", "[Ljava/lang/reflect/Method;", null, null));
        //添加方法setLogic,用於設置Logic
        MethodNode mn = new MethodNode(Opcodes.ACC_PUBLIC, "setLogic", "(L" + logicPkg + "AddLogic;)V", null, null);
        //下面的指令至關於this.logic = logic;
        InsnList insnList = mn.instructions;
        
        insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
        
        insnList.add(new VarInsnNode(Opcodes.ALOAD, 1));
        
        insnList.add(new FieldInsnNode(Opcodes.PUTFIELD, descInfoPrefix + "$Proxy", "logic", "L" + logicPkg + "AddLogic;"));
        
        insnList.add(new InsnNode(Opcodes.RETURN));
        
        mn.maxLocals = 2;//定義最大的本地變量
        
        mn.maxStack = 2;//定義最大的操做數棧
        
        list.add(mn);
        //添加一個setMethods方法,用於設置methods字段
        MethodNode meth = new MethodNode(Opcodes.ACC_PUBLIC, "setMethods", "([Ljava/lang/reflect/Method;)V", null, null);
        //這段指令至關於this.methods = methods;
        InsnList methList = meth.instructions;
        
        methList.add(new VarInsnNode(Opcodes.ALOAD, 0));
        
        methList.add(new VarInsnNode(Opcodes.ALOAD, 1));
        
        methList.add(new FieldInsnNode(Opcodes.PUTFIELD, descInfoPrefix + "$Proxy", "methods", "[Ljava/lang/reflect/Method;"));
        
        methList.add(new InsnNode(Opcodes.RETURN));
        
        meth.maxLocals = 2;
        
        meth.maxStack = 2;
        
        list.add(meth);//
        //添加構造方法
        MethodNode init = new MethodNode(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        //這裏是調用父類構造器,至關於super();
        InsnList initList = init.instructions;
        
        initList.add(new VarInsnNode(Opcodes.ALOAD, 0));
        
        initList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, descInfoPrefix + "SayHello", "<init>", "()V", false));
        
        initList.add(new InsnNode(Opcodes.RETURN));
        
        init.maxLocals = 1;
        
        init.maxStack = 1;
        
        list.add(init);
        
        int count = 0;
        //循環建立須要代理的方法
        for (Method method : methods) {
            
            MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC,  method.getName(), DescInfo.getDescInfo(method), null, null);
            
//            System.err.println(DescInfo.getDescInfo(method));
            
            InsnList il = methodNode.instructions;
            
            //得到參數的類型
            Class<?>[] clazz = method.getParameterTypes();
            
            //計算出參數會佔用掉的本地變量表長度,long,double類型佔用兩個slot
            int len = LocalLen.len(clazz);
            //得到這個方法的參數個數,不包括this
            int size = clazz.length;
            //或的返回值類型
            Class<?> rtClazz = method.getReturnType();
            
            il.clear();
            /**
             * 下面的一大段指令的意思是
             * int index = count;
             * Method method = methods[index];//從methods中得到對應的方法對象
             * Object[] objs = new Object[]{arg0,arg1,arg2....};用來存方法傳過來的參數值的
             * try {
             *         logic.addLogic(method, objs);
             * } catch(Exception e) {
             *         e.printStackTrace();
             * }
             */
            il.add(new LdcInsnNode(count));//
            
            il.add(new VarInsnNode(Opcodes.ISTORE, len + 1));
            
            il.add(new VarInsnNode(Opcodes.ALOAD, 0));
            
            il.add(new FieldInsnNode(Opcodes.GETFIELD, descInfoPrefix + "$Proxy", "methods", "[Ljava/lang/reflect/Method;"));
            
            il.add(new VarInsnNode(Opcodes.ILOAD, len + 1));
            
            il.add(new InsnNode(Opcodes.AALOAD));
            
            il.add(new VarInsnNode(Opcodes.ASTORE, len + 2));//將棧頂的method存到局部變量表中
            
            //將參數長度推到棧頂
            il.add(new LdcInsnNode(size));
            
            il.add(new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object"));//new 出一個Object的數組
            
            il.add(new VarInsnNode(Opcodes.ASTORE, len + 3));//將數組存到本地變量表中
            
            int index = 1;
            
            //將參數值全都存到數組中
            for(int i = 0; i < size; i++) {
                
                il.add(new VarInsnNode(Opcodes.ALOAD, len + 3));//將數組推到棧頂
                
                il.add(new LdcInsnNode(i));//下標
                
                int opcode = OpcodeMap.getOpcodes(clazz[i].getName());//得到當前是什麼類型的參數,使用什麼樣類型的指令
                //若是是long,double類型的index加2
                if(opcode == 22 || opcode == 24) {
                    
                    il.add(new VarInsnNode(opcode,index));//將long或者double參數推到棧頂
                    index += 2;
                } else {
                    
                    il.add(new VarInsnNode(opcode, index));//將參數推到棧頂
                    index += 1;
                }
                
                
                if(AutoPKG.auto(clazz[i].getName()) != null) {
                    
                    il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, AutoPKG.auto(clazz[i].getName()), "valueOf", AutoPKG_valueOf.auto(clazz[i].getName()), false));
                    
                }
                
                
                il.add(new InsnNode(Opcodes.AASTORE));//將數據存到數組中
                
            }
            
            
            il.add(new VarInsnNode(Opcodes.ALOAD, 0));//
            
            il.add(new FieldInsnNode(Opcodes.GETFIELD, descInfoPrefix + "$Proxy", "logic", "L" + logicPkg + "AddLogic;"));
            
            il.add(new VarInsnNode(Opcodes.ALOAD, len+2));
            
            il.add(new VarInsnNode(Opcodes.ALOAD, len + 3));
            
            il.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, "" + logicPkg + "AddLogic", "addLogic", "(Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true));
            
            il.add(new TypeInsnNode(Opcodes.CHECKCAST, rtClazz.getName().replace(".", "/")));
            
            LabelNode label = new LabelNode();
            
            il.add(new JumpInsnNode(Opcodes.GOTO, label));
            //因爲對棧圖仍是不太明白是啥意思,若是有知道的麻煩告知我一聲
            //il.add(new FrameNode(Opcodes.F_SAME, 0, null, 0, null));
            
            il.add(new VarInsnNode(Opcodes.ASTORE, len + 4));
            
            il.add(new VarInsnNode(Opcodes.ALOAD, len + 4));
            
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false));
            
            il.add(label);
            
            il.add(new InsnNode(OpcodeRt.getOpcodes(rtClazz.getName())));
            
            methodNode.maxLocals = 5+len;
            
            methodNode.maxStack = 5;
            
            list.add(methodNode);
            
            count ++;
        }
        
        return cn;
    }
    
    

}

2、有了生成代理的類,那麼就還應該有個處理邏輯的接口數組

package com.asm_core.logic;

import java.lang.reflect.Method;

/**
 * 這個類用來增長方法邏輯,相似於JDK代理的InvocationHandler
 * @author may
 *
 */
public interface AddLogic {
    
    /**
     * 
     * @param method 被代理對象的方法對象
     * @param args 被代理方法的參數
     * @throws Exception
     */
    public Object addLogic(Method method, Object[] args) throws Exception ;

}

3、若是方法參數中存在基本類型參數,要自動打包成Object[] args,寫個基本類型對應包裝類助手ide

package com.asm_core;

import java.util.HashMap;
import java.util.Map;

public class AutoPKG {
    
    public static final Map<String, String> map = new HashMap<>();
    
    static {
        map.put("int", "java/lang/Integer");
        
        map.put("byte", "java/lang/Byte");
        
        map.put("short", "java/lang/Short");
        
        map.put("long", "java/lang/Long");
        
        map.put("boolean", "java/lang/Boolean");
        
        map.put("char", "java/lang/Character");
        
        map.put("float", "java/lang/Float");
        
        map.put("double", "java/lang/Double");
        
    }
    
    public static String auto(String type) {
        
        if(map.containsKey(type)) {
            
            return map.get(type);
        } else {
            
            
            return null;
        }
        
        
    }

}

 

4、基本類型對應包裝類的valueOf方法的描述符工具

package com.asm_core;

import java.util.HashMap;
import java.util.Map;

public class AutoPKG_valueOf {
    
    public static final Map<String, String> map = new HashMap<>();
    
    static {
        map.put("int", "(I)Ljava/lang/Integer;");
        
        map.put("byte", "(B)Ljava/lang/Byte;");
        
        map.put("short", "(S)Ljava/lang/Short;");
        
        map.put("long", "(J)Ljava/lang/Long;");
        
        map.put("boolean", "(Z)Ljava/lang/Boolean;");
        
        map.put("char", "(C)Ljava/lang/Character;");
        
        map.put("float", "(F)Ljava/lang/Float;");
        
        map.put("double", "(D)Ljava/lang/Double;");
        
    }
    
    public static String auto(String type) {
        
        if(map.containsKey(type)) {
            
            return map.get(type);
        } else {
            
            return null;
        }
        
        
    }

}

5、方法描述符生成助手測試

package com.asm_core;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 用於生成字節碼描述符的工具類
 * @author may
 *
 */
public class DescInfo {
    
    public static final Map<String, String> map = new HashMap<>();
    
    static {
        
        map.put("int", "I");
        
        map.put("byte", "B");
        
        map.put("short", "S");
        
        map.put("long", "J");
        
        map.put("boolean", "Z");
        
        map.put("char", "C");
        
        map.put("float", "F");
        
        map.put("double", "D");
        
        map.put("void", "V");
        
    }
    
    public static String getDescInfo(Method method) {
        
        Class<?>[]  pt = method.getParameterTypes();
        
        Class<?>  rt = method.getReturnType();
        
        String rtStr = "V";
        
        if(map.containsKey(rt.getName())) {
            
            rtStr = map.get(rt.getName());
            
        } else {
            /**
             * 若是不爲空,那麼就是數組
             */
            Class<?> clazz= rt.getComponentType();//若是原來的rt是int[][]
            Class<?> oldClazz = clazz;//int[]
            int count = 0;
            if(oldClazz != null) {
                rtStr = "";
                while(clazz != null) {
                    count ++;//2
                    oldClazz = clazz;
                    clazz= clazz.getComponentType();
                }
                for(int i = 0; i < count; i++) {
                    rtStr += "[";
                    
                }
                if(map.containsKey(oldClazz.getName())) {
                    rtStr += map.get(oldClazz.getName());
                } else {
                    rtStr += "L" + oldClazz.getName().replace(".", "/") + ";";
                }
            } else {
                
                rtStr = "L" + rt.getName().replace(".", "/") + ";";
            }
        }
        
        
        String descInfo = "(";
        
        for (Class<?> class1 : pt) {
            String name = class1.getName();
            
            if(map.containsKey(name)) {
                descInfo += map.get(name);
                
            } else {
                if(class1.getComponentType() != null) {
                    descInfo += class1.getName().replace(".", "/");
                    
                } else {
                    
                    descInfo += ("L" + name.replace(".", "/") + ";");
                }
            }
            
        }
        descInfo += ")" + rtStr;
        return descInfo;
        
    }
    

}

6、用於計算局部變量表的slot長度this

package com.asm_core;

/**
 * 計算本地變量表的長度,long,double類型會佔用兩個slot
 * @author may
 *
 */
public class LocalLen {
    
    
    public static int len(Class<?>[] clzz) {
        
        int count = 0;
        
        for (Class<?> class1 : clzz) {
            
            String str = class1.getName();
            if(str.equals("long") || str.equals("double")) {
                
                count += 2;
                
            } else {
                
                count ++;
            }
        }
        
        return count;
    }
    
}

7、根據不一樣類型使用不一樣字節碼指令助手類spa

package com.asm_core;

import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.Opcodes;

public class OpcodeMap {

    public static Map<String, Integer> map = new HashMap<>();
    
    static {
        
        
        map.put("int", Opcodes.ILOAD);
        
        map.put("byte", Opcodes.ILOAD);
        
        map.put("short", Opcodes.ILOAD);
        
        map.put("long", Opcodes.LLOAD);
        
        map.put("boolean", Opcodes.ILOAD);
        
        map.put("char", Opcodes.ILOAD);
        
        map.put("float", Opcodes.FLOAD);
        
        map.put("double", Opcodes.DLOAD);
        
        
    }
    
    public static int getOpcodes(String type) {
        
        if(map.containsKey(type)) {
            
            return map.get(type);
            
        } else {
            
            
            return Opcodes.ALOAD;
        }
        
    }
    
}

8、根據不一樣的返回類型使用不一樣字節碼指令的助手類debug

package com.asm_core;

import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.Opcodes;

public class OpcodeRt {

    public static Map<String, Integer> map = new HashMap<>();
    
    static {
        
        
        map.put("int", Opcodes.IRETURN);
        
        map.put("byte", Opcodes.IRETURN);
        
        map.put("short", Opcodes.IRETURN);
        
        map.put("long", Opcodes.LRETURN);
        
        map.put("boolean", Opcodes.IRETURN);
        
        map.put("char", Opcodes.IRETURN);
        
        map.put("float", Opcodes.FRETURN);
        
        map.put("double", Opcodes.DRETURN);
        
        map.put("void", Opcodes.RETURN);
        
        
    }
    
    public static int getOpcodes(String type) {
        
        if(map.containsKey(type)) {
            
            return map.get(type);
            
        } else {
            
            
            return Opcodes.ARETURN;
        }
        
    }
    
}

9、自定義類加載器代理

package com.asm_core;

public class MyClassLoader extends ClassLoader {
    
    private byte[] b = null;
    
    public MyClassLoader(byte[] b) {
        
        this.b = b;
        
    }
    
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        
        return this.defineClass(name, b, 0, b.length);
    }

}

10、測試類

1】實現邏輯接口

package com.asm_core.test;

import java.lang.reflect.Method;

import com.asm_core.logic.AddLogic;

/**
 * 實現了方法邏輯的類
 * @author may
 *
 */
public class AddLogicImpl implements AddLogic {
    
    private Object sayHello;
    
    public AddLogicImpl(Object sayHello) {
        
        this.sayHello = sayHello;
        
    }

    @Override
    public Object addLogic(Method method, Object[] args) throws Exception {
        
        System.out.println("Hello");
        Object obj = method.invoke(sayHello, args);//咱們能夠在調用目標方法的周圍增長邏輯
        System.out.println("baby");
        return obj;
    }

}

2】被代理類

package com.asm_core.test;

import java.util.Date;

/**
 * 須要進行代理的類
 * @author Administrator
 *
 */
public class SayHello {
    
    
    public void sayHello(String str_1, String str_2, int age, String[] args) {
        
        System.out.println(str_1 + " " + str_2 + "嘿嘿:" + age);
        
    }
    
    private String l() {
        System.out.println("private String l() {");
        return "";
        
    }
    
    public int[][] tt(int age, long l, double d) {
        System.out.println("public int[][] tt(int age) {");
        return new int[][]{};
    }
    
    public String[][] hh(long k, double d, Double dd) {
        System.out.println("public String[][] hh(long k, double d, Double dd) {");
        return null;
    }
    
    
    public String[][] hh(short age, byte[] arg, int a, float f, char c, long l, double d, int[][] ii, String str, String[][] ss, Date date) {
        System.out.println("public String[][] hh(short age, byte[] arg, int a, float f, char c, long l, double d, int[][] ii, String str, String[][] ss, Date date) {");
        return null;
    }
    
    /*public String[][] hh(Long l, Double d) {
        System.out.println("public String[][] hh(short age, byte[] arg, double d) {");
        return null;
    }*/


    
    
}

3】Test

package com.asm_core.test;

import java.util.Date;

import com.asm_core.GenSubProxy;
import com.asm_core.logic.AddLogic;

public class Test {
    
public static void main(String[] args) {
        
        SayHello sayHello = new SayHello();
        
        AddLogic logic = new AddLogicImpl(sayHello);
        
        GenSubProxy genSubProxy = new GenSubProxy(logic);
        
        Object obj = genSubProxy.genSubProxy(SayHello.class);
        
        SayHello sh = (SayHello) obj;
        
        sh.hh((byte)1, new byte[]{}, 1, 1f, 's',1, 1, new int[][]{{12}}, "", new String[][]{{"sdf","s"}}, new Date());
        
        sh.sayHello("sg", "srt", 234, new String[]{});
        
    }

}

11、總結

使用ASM實現動態代理,須要先學懂JVM虛擬機的字節碼指令。在本身寫字節碼指令的時候,若是你忘記了某些代碼的指令的實現,別忘記使用JDK的javap -c -v -private **.class。經過javap咱們能夠解決好多咱們曾經感到疑惑的地方,好比爲何匿名內部類使用局部變量時這個局部變量不能變?爲何在字節碼層面上不能直接將基本類型複製給Object類型?synchronized字節碼中如何表述的。。。。。。

相關文章
相關標籤/搜索