ASM代理的那些事

关于代理

大家应该知道AOP,著名的Spring 框架中实现了拦截等功能,主要通过AOP的方式实现的,简而言之就是桩,在方法前后增加拦截,如:before,after等功能;

最简单的Proxy 实现;大家先看看有个简单认识:

package Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Objects;

/**
 * Copyright (c) 2017/3/16 (Qinjin ashang.peng@aliyun.com)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class UProxy {

    public static void main(String[] args) {
        TestProxy proxy = new TestProxyImpl();
        proxy = (TestProxy) UHandler.getInstance(proxy);
        TestProxy proxy2 = new TestProxyImpl();
        proxy2 = (TestProxy) UHandler.getInstance(proxy2);
        proxy.test();
        proxy2.test();
        proxy2.test();
        proxy.test();
    }
}

class UHandler implements InvocationHandler {
    private Object target = null;

    UHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("start");
        System.out.println(method.getDeclaringClass() + ":" + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("end");
        return result;
    }

    public static Object getInstance(Object source) {
        UHandler handler = new UHandler(source);
        return Proxy.newProxyInstance(source.getClass().getClassLoader(), source.getClass().getInterfaces(), handler);
    }
}

interface TestProxy {
    public void test();
}

class TestProxyImpl implements TestProxy {
    public void test() {
        System.out.println("999999999999");
    }
}

执行结果:

start
interface Test.TestProxy:test
999999999999
end
start
interface Test.TestProxy:test
999999999999
end
start
interface Test.TestProxy:test
999999999999
end
start
interface Test.TestProxy:test
999999999999
end

传说中的性能指标

这是载自“http://javatar.iteye.com/blog/814426/”找到的一个说各种代理模式的性能,其实也就那个样,上面说proxy 性能一般,不是最好也不是最差的。

下面是一份报告,大家可以参考参考,因为jdk proxy 是动态代理,所以稍微慢点,如果生成class类模式的呢,应该就和静态代码执行的效率差不多了,下面介绍下ASM.

Create JDK Proxy: 7 ms
Create CGLIB Proxy: 86 ms
Create JAVAASSIST Proxy: 36 ms
Create JAVAASSIST Bytecode Proxy: 57 ms
Create ASM Proxy: 1 ms
================
Run JDK Proxy: 235 ms, 6,000,278 t/s
Run CGLIB Proxy: 234 ms, 6,025,920 t/s
Run JAVAASSIST Proxy: 459 ms, 3,072,037 t/s
Run JAVAASSIST Bytecode Proxy: 71 ms, 19,860,076 t/s
Run ASM Bytecode Proxy: 72 ms, 19,584,241 t/s
----------------
Run JDK Proxy: 298 ms, 4,731,763 t/s
Run CGLIB Proxy: 134 ms, 10,522,876 t/s
Run JAVAASSIST Proxy: 406 ms, 3,473,067 t/s
Run JAVAASSIST Bytecode Proxy: 67 ms, 21,045,752 t/s
Run ASM Bytecode Proxy: 66 ms, 21,364,627 t/s
----------------
Run JDK Proxy: 282 ms, 5,000,231 t/s
Run CGLIB Proxy: 133 ms, 10,601,995 t/s
Run JAVAASSIST Proxy: 406 ms, 3,473,067 t/s
Run JAVAASSIST Bytecode Proxy: 67 ms, 21,045,752 t/s
Run ASM Bytecode Proxy: 67 ms, 21,045,752 t/s
----------------

ASM

 ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

推荐个Maven :

<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.0_ALPHA</version>
</dependency>

ASM框架中的核心类有以下几个:

  ①  ClassReader:该类用来解析编译过的class字节码文件。

  ②  ClassWriter:该类用来重新构建编译后的类,比如说修改类名、属性以及方法,甚至可以生成新的类的字节码文件。

  ③  ClassAdapter:该类也实现了ClassVisitor接口,它将对它的方法调用委托给另一个ClassVisitor对象。

ASM 能够通过改造既有类,直接生成需要的代码。增强的代码是硬编码在新生成的类文件内部的,没有反射带来性能上的付出。同时,ASM 与 Proxy 编程不同,不需要为增强代码而新定义一个接口,生成的代码可以覆盖原来的类,或者是原始类的子类。它是一个普通的 Java 类而不是 proxy 类,甚至可以在应用程序的类框架中拥有自己的位置,派生自己的子类。

相比于其他流行的 Java 字节码操纵工具,ASM 更小更快。ASM 具有类似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分别有 350k 和 150k。同时,同样类转换的负载,如果 ASM 是 60% 的话,BCEL 需要 700%,而 SERP 需要 1100% 或者更多。

ASM 已经被广泛应用于一系列 Java 项目:AspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、SonarJ、Substance L&F、Retrotranslator 等。Hibernate 和 Spring 也通过 cglib,另一个更高层一些的自动代码生成工具使用了 ASM。

上面是个各种业务的比较,可以参考。具体的下篇文章给大家介绍。返回正题,既然主题说Load proxy ,下面给大家一个asm proxy的例子;

ASM实例

首先来个Factory

package me.duzhi.aop.asm;

/**
 * <p>
 * asm 代理工厂
 * Copyright (c) 2017/3/16 (Qinjin ashang.peng@aliyun.com)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;


public class AsmFactory {
    public static final String SUFIX = "$EnhancedByCc";
    public static BytecodeLoader classLoader = new BytecodeLoader();

    /**
     *
     * <p>
     * 根据字节码加载class
     * </p>
     *
     * @author dixingxing
     * @date Apr 29, 2012
     */
    public static class BytecodeLoader extends ClassLoader {
        public Class<?> defineClass(String className, byte[] byteCodes) {
            return super.defineClass(className, byteCodes, 0, byteCodes.length);
        }
    }


    /**
     *
     * <p>
     * 返回代理类
     * </p>
     *
     * @param <T>
     * @param clazz
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> getEnhancedClass(Class<T> clazz){
        String enhancedClassName = clazz.getName() + SUFIX;
        try {
            return (Class<T>) classLoader.loadClass(enhancedClassName);
        } catch (ClassNotFoundException classNotFoundException) {
            ClassReader reader = null;
            try {
                reader = new ClassReader(clazz.getName());
            } catch (IOException ioexception) {
                throw new RuntimeException(ioexception);
            }
            ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            ClassVisitor visitor = new ClassAdapter(enhancedClassName, clazz,
                    writer);
            reader.accept(visitor, 0);
            byte[] byteCodes = writer.toByteArray();
            Class<T> result = (Class<T>) classLoader.defineClass(
                    enhancedClassName, byteCodes);
            return result;
        }
    }

    /**
     *
     * <p>
     * 把java字节码写入class文件
     * </p>
     *
     * @param <T>
     * @param name
     * @param data
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static <T> void writeClazz(String name, byte[] data) {
        try {
            File file = new File("temp/" + name + ".class");
            FileOutputStream fout = new FileOutputStream(file);
            fout.write(data);
            fout.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



}

来个Class Adapter;

package me.duzhi.aop.asm;

import me.duzhi.aop.HandlerFactory;
import org.objectweb.asm.*;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * Copyright (c) 2017/3/16 (Qinjin ashang.peng@aliyun.com)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class ClassAdapter extends ClassVisitor implements Opcodes {
    public static final String INIT = "<init>";
    private ClassWriter classWriter;
    private String originalClassName;
    private String enhancedClassName;
    private Class<?> originalClass;

    public ClassAdapter(String enhancedClassName,
                        Class<?> targetClass, ClassWriter writer) {
        super(Opcodes.ASM6,writer);
        this.classWriter = writer;
        this.originalClassName = targetClass.getName();
        this.enhancedClassName = enhancedClassName;
        this.originalClass = targetClass;
    }

    @Override
    public void visit(int version, int access, String name, String signature,
                      String superName, String[] interfaces) {
        cv.visit(version, Opcodes.ACC_PUBLIC,
                toAsmCls(enhancedClassName), signature, name,
                interfaces);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc,
                                   String signature, Object value) {
        // 拷贝所有属性   可使用java反射给属性赋值(生成class后newInstance在赋值)
        return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
                                     String signature, String[] exceptions) {
        // 删除所有方法
        return null;
    }

    /**
     * 把类名中的.替换为/
     * @param className
     * @return
     */
    private static String toAsmCls(String className) {
        return className.replace('.', '/');
    }

    /**
     *
     * <p>前置方法</p>
     *
     * @see TxHandler
     * @param mWriter
     * @param m
     */
    private static void doBefore(MethodVisitor mWriter, String methodInfo, Method m) {
        mWriter.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
        mWriter.visitLdcInsn("before method : " + methodInfo);

        mWriter.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
                "(Ljava/lang/String;)V", false);
        // 或者直接调用静态方法
         mWriter.visitLdcInsn(methodInfo);
         mWriter.visitMethodInsn(INVOKESTATIC,toAsmCls(HandlerFactory.class.getName()),"before","(Ljava/lang/String;)V",false);
        mWriter.visitLdcInsn(methodInfo);
        mWriter.visitMethodInsn(INVOKESTATIC,toAsmCls(HandlerFactory.class.getName()),"before","(Ljava/lang/String;)V",false);
    }



    /**
     *
     * <p>后置方法</p>
     * @see TxHandler
     * @param mWriter
     */
    private static void doAfter(MethodVisitor mWriter,String methodInfo) {
        mWriter.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
        mWriter.visitLdcInsn("after method : " + methodInfo);
        mWriter.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
                "(Ljava/lang/String;)V");
    }


    /**
     *
     * <p>
     * object类本身的方法不做重写
     * </p>
     * <p>
     * "main" 方法不做重写
     * </p>
     *
     * @param m
     * @return
     */
    public static boolean needOverride(Method m) {
        // object类本身的方法不做重写
        if (m.getDeclaringClass().getName().equals(Object.class.getName())) {
            return false;
        }
        // "main" 方法不做重写
        if (Modifier.isPublic(m.getModifiers())
                && Modifier.isStatic(m.getModifiers())
                && m.getReturnType().getName().equals("void")
                && m.getName().equals("main")) {
            return false;
        }

        return true;
    }

    @Override
    public void visitEnd() {
        // 如果originalClass定义了私有成员变量,那么直接在visitMethod中复制originalClass的<init>会报错。
//		ALOAD 0
//		INVOKESPECIAL cc/RoleService.<init>()V
//		RETURN
//		// 调用originalClassName的<init>方法,否则class不能实例化
        MethodVisitor mvInit = classWriter.visitMethod(ACC_PUBLIC, INIT, "()V", null, null);
        mvInit.visitVarInsn(ALOAD, 0);
        mvInit.visitMethodInsn(INVOKESPECIAL, toAsmCls(originalClassName), INIT, "()V");
        mvInit.visitInsn(RETURN);
        mvInit.visitMaxs(0, 0);
        mvInit.visitEnd();


        // 获取所有方法,并重写(main方法 和 Object的方法除外)
        Method[] methods = originalClass.getMethods();
        for(Method m : methods) {
            if(!needOverride(m)) {
                continue;
            }
            Type mt = Type.getType(m);
            StringBuilder methodInfo = new StringBuilder(originalClassName);
            methodInfo.append(".").append(m.getName());
            methodInfo.append("|");
            Class<?>[] paramTypes = m.getParameterTypes();
            for(Class<?> t : paramTypes) {
                methodInfo.append(t.getName()).append(",");
            }
            if(paramTypes.length > 0) {
                methodInfo.deleteCharAt(methodInfo.length() - 1);
            }

            // 方法是被哪个类定义的
            String declaringCls = toAsmCls(m.getDeclaringClass().getName());

            // 方法 description
            MethodVisitor mWriter = classWriter.visitMethod(ACC_PUBLIC, m.getName(), mt.toString(), null, null);

            // insert code here (before)
            doBefore(mWriter,methodInfo.toString(),m);


            int i = 0;
            // 如果不是静态方法 load this对象
            if(!Modifier.isStatic(m.getModifiers())) {
                mWriter.visitVarInsn(ALOAD, i++);
            }
            StringBuilder sb = new StringBuilder(m.getName());
            // load 出方法的所有参数
            for(Class<?> tCls : m.getParameterTypes()) {
                Type t = Type.getType(tCls);
                sb.append(loadCode(t)).append(",");
                mWriter.visitVarInsn(loadCode(t), i++);
                // long和double 用64位表示,要后移一个位置,否则会报错
                if(t.getSort() == Type.LONG || t.getSort() == Type.DOUBLE) {
                    i++;
                }
            }


            // super.xxx();
            mWriter.visitMethodInsn(INVOKESPECIAL,declaringCls,m.getName(),mt.toString());

            // 处理返回值类型
            Type rt = Type.getReturnType(m);
            // 没有返回值
            if(rt.toString().equals("V")) {
                doAfter(mWriter,methodInfo.toString());
                mWriter.visitInsn(RETURN);
            }
            // 把return xxx() 转变成 : Object o = xxx(); return o;
            else {
                int storeCode = storeCode(rt);
                int loadCode = loadCode(rt);
                int returnCode = rtCode(rt);

                mWriter.visitVarInsn(storeCode, i);
                doAfter(mWriter,methodInfo.toString());
                mWriter.visitVarInsn(loadCode, i);
                mWriter.visitInsn(returnCode);
            }

            // 已设置了自动计算,但还是要调用一下,不然会报错
            mWriter.visitMaxs(i, ++i);
            mWriter.visitEnd();
        }
        cv.visitEnd();
    }

    /**
     *
     * <p>get StoreCode(Opcodes#xStore)</p>
     *
     *
     * @param type
     * @return
     */
    public static int storeCode(Type type) {

        int sort = type.getSort();
        switch (sort) {
            case Type.ARRAY:
                sort = ASTORE;
                break;
            case Type.BOOLEAN:
                sort = ISTORE;
                break;
            case Type.BYTE:
                sort = ISTORE;
                break;
            case Type.CHAR:
                sort = ISTORE;
                break;
            case Type.DOUBLE:
                sort = DSTORE;
                break;
            case Type.FLOAT:
                sort = FSTORE;
                break;
            case Type.INT:
                sort = ISTORE;
                break;
            case Type.LONG:
                sort = LSTORE;
                break;
            case Type.OBJECT:
                sort = ASTORE;
                break;
            case Type.SHORT:
                sort = ISTORE;
                break;
            default:
                break;
        }
        return sort;
    }

    /**
     *
     *  <p>get StoreCode(Opcodes#xLOAD)</p>
     *
     * @param type
     * @return
     */
    public static int loadCode(Type type) {
        int sort = type.getSort();
        switch (sort) {
            case Type.ARRAY:
                sort = ALOAD;
                break;
            case Type.BOOLEAN:
                sort = ILOAD;
                break;
            case Type.BYTE:
                sort = ILOAD;
                break;
            case Type.CHAR:
                sort = ILOAD;
                break;
            case Type.DOUBLE:
                sort = DLOAD;
                break;
            case Type.FLOAT:
                sort = FLOAD;
                break;
            case Type.INT:
                sort = ILOAD;
                break;
            case Type.LONG:
                sort = LLOAD;
                break;
            case Type.OBJECT:
                sort = ALOAD;
                break;
            case Type.SHORT:
                sort = ILOAD;
                break;
            default:
                break;
        }
        return sort;
    }

    /**
     *
     *  <p>get StoreCode(Opcodes#xRETURN)</p>
     *
     * @param type
     * @return
     */
    public static int rtCode(Type type) {
        int sort = type.getSort();
        switch (sort) {
            case Type.ARRAY:
                sort = ARETURN;
                break;
            case Type.BOOLEAN:
                sort = IRETURN;
                break;
            case Type.BYTE:
                sort = IRETURN;
                break;
            case Type.CHAR:
                sort = IRETURN;
                break;
            case Type.DOUBLE:
                sort = DRETURN;
                break;
            case Type.FLOAT:
                sort = FRETURN;
                break;
            case Type.INT:
                sort = IRETURN;
                break;
            case Type.LONG:
                sort = LRETURN;
                break;
            case Type.OBJECT:
                sort = ARETURN;
                break;
            case Type.SHORT:
                sort = IRETURN;
                break;
            default:
                break;
        }
        return sort;
    }
}

TestCase

package aop.impl;

/**
 * Copyright (c) 2017/3/16 (Qinjin ashang.peng@aliyun.com)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class RoleService {
    public void executeOuter(int i) {
        System.out.println("1111111");
    }
}
package aop;

import aop.impl.RoleService;
import me.duzhi.aop.asm.AsmFactory;

/**
 * Copyright (c) 2017/3/16 (Qinjin ashang.peng@aliyun.com)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class TestAsm {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Class<RoleService> rsCls = AsmFactory.getEnhancedClass(RoleService.class);
        rsCls.newInstance().executeOuter(1);
    }
}

output:

before method : aop.impl.RoleService.executeOuter|int
be:aop.impl.RoleService.executeOuter|int
be:aop.impl.RoleService.executeOuter|int
1111111
after method : aop.impl.RoleService.executeOuter|int

ok,收工。

除特别注明外,本站所有文章均为duzhi原创,转载请注明出处来自https://www.duzhi.me/article/2500.html

联系我们

******

在线咨询:点击这里给我发消息

邮件:ashang.peng#aliyun.com

QR code