ASM、Javassist、JDK、CGLIB

ASM/Javassist/JDK/CGLIB

简介

ASM是一款基于java字节码层面的代码分析和修改工具。

ASM的目标是生成,转换和分析已编译的java class文件,可使用ASM工具读/写/转换JVM指令集。

ASM工具提供两种方式来产生和转换已编译的class文件,它们分别是基于事件和基于对象的表示模型。

其中,基于事件的表示模型使用一个有序的事件序列表示一个class文件,class文件中的每一个元素使用一个事件来表示,比如class的头部,变量,方法声明,JVM指令都有相对应的事件表示

ASM使用自带的事件解析器能将每一个class文件解析成一个事件序列。而基于对象的表示模型则使用对象树结构来解析每一个class文件。

起点和终点分别是ClassReader(Class文件解析器)、ClassWriter(class事件序列转换到class字节码),中间的过程由若干个自定义的事件过滤器组成

JAVA动态性的两种常见实现方式

  • 字节码操作
  • 反射

运行时操作字节码可以让我们实现如下功能

  • 动态生成新的类
  • 动态改变某个类的结构(添加/删除/修改/添加新的属性/方法)

常见的字节码操作类库

  • BCEL
  • ASM
  • CGLIB
  • javassist

操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit 。

ASM :直接操作字节码指令,执行效率高,要是使用者掌握Java字节码文件格式及指令,对使用者的要求比较高。

扫描二维码关注公众号,回复: 12663114 查看本文章

Javassit :提供了更高级的API,执行效率相对较差,对使用者要求较低.

ASM转换原理

ASM工具生产和转换class文件内容的所有工作都是基于ClassVisitor这个抽象类进行的。

ClassVisitor抽象类中的每一个方法会对应到class文件的相应区域,每个方法负责处理class文件相应区域的字节码内容

我们可以把继承ClassVisitor的抽象类的自定义类看作负责class文件各个区域内容的修改和生成

1、遍历Class字节码类信息

//官网案例
public class ClassPrinter extends ClassVisitor {
    public ClassPrinter() {
        super(ASM4);
    }
    public void visit(int version, int access, String name,
        String signature, String superName, String[] interfaces) {
    System.out.println(name + " extends " + superName + " {");
    }
    public void visitSource(String source, String debug) {}
    public void visitOuterClass(String owner, String name, String desc) {}
    public AnnotationVisitor visitAnnotation(String desc,boolean visible) {
        return null;
    }
    public void visitAttribute(Attribute attr) {
    }
    public void visitInnerClass(String name, String outerName,
    String innerName, int access) {
    }
    public FieldVisitor visitField(int access, String name, String desc,String signature,       Object value) {
        System.out.println(" " + desc + " " + name);
        return null;
    }
    public MethodVisitor visitMethod(int access, String name,
    String desc, String signature, String[] exceptions) {
        System.out.println(" " + name + desc);
        return null;
    }
    public void visitEnd() {
        System.out.println("}");
    }
}
​
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
​

result:
    java/lang/Runnable extends java/lang/Object {
        run()V
    }

2、生成自定义类对应的class字节码内容

package pkg;
public interface Comparable extends Mesurable {
    int LESS = -1;
    int EQUAL = 0;
    int GREATER = 1;
    int compareTo(Object o);
}

test.java内容:

ClassWriter cw = new ClassWriter(0);
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/Comparable", null, "java/lang/Object", new String[] { "pkg/Mesurable" });
​
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1)).visitEnd();
​
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0)).visitEnd();
​
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, new Integer(1)).visitEnd(); cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null).visitEnd();
​
cw.visitEnd();
byte[] b = cw.toByteArray();

3、动态加载2生产出的class字节码并实例化该类,使用继承自ClassLoader的类,并重写defineClass方法

//第一种方法:通过ClassLoader的defineClass动态加载字节码
class MyClassLoader extends ClassLoader {
    public Class defineClass(String name, byte[] b) {
        return defineClass(name, b, 0, b.length);
    }
}
//直接调用方法
Class c = myClassLoader.defineClass("pkg.Comparable", b);

使用继承自ClassLoader的类,并重写findClass内部类

class StubClassLoader extends ClassLoader {
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        if (name.endsWith("_Stub")) {
            ClassWriter cw = new ClassWriter(0);
...
        byte[] b = cw.toByteArray();
            return defineClass(name, b, 0, b.length);
        }
        return super.findClass(name);
    }
}

4、ClassReader生产者生产的class字节码bytes可以被ClassWriter直接消费

byte[] b1 = ...;
ClassWriter cw = new ClassWriter(0);
ClassReader cr = new ClassReader(b1);
cr.accept(cw, 0);
byte[] b2 = cw.toByteArray(); //这里的b2与b1表示同一个类且值一样

5、ClassReader生产者生产的字节码bytes可以先被继承自ClassVisitor的自定义类过滤,最后被ClassWrite消费

byte[] b1 = ...;
ClassWriter cw = new ClassWriter(0);
// cv forwards all events to cw
ClassVisitor cv = new ChangeVersionAdapter (cw) { };
ClassReader cr = new ClassReader(b1);
cr.accept(cv, 0);
byte[] b2 = cw.toByteArray(); //这里的b2与b1表示同一个类但值不一样

整个字节码转换过程的时序图如下

public class ChangeVersionAdapter extends ClassVisitor { //只是继承了ClassVisitor
    public ChangeVersionAdapter(ClassVisitor cv) {
        super(ASM4, cv);
    }
    @Override       //重写visit()
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {   
        //过滤出类中的方法并指定方法的版本号(v1.5)
        cv.visit(V1_5, access, name, signature, superName, interfaces);
    } 
}

相比Javassist

  java字节码以二进制的形式存储在.class文件中,每一个.class文件包含一个java类或者接口。

  javassist就是一个用来处理java字节码的类库。可以在一个编译好的类中添加方法(也就是我们说的增强),或者修改已有方法。

而且也可以生成一个新的类对象,手动方式。

在javassist中,类Javassist.CtClass表示class文件。

ClassPool是CtClass对象的容器。ClassPool会在内存中维护创建过的CtClass对象,该对象过多时,占有内存也就会多,可以调用CtClass的detach()方法释放内存

CtClass相关方法:

  1. freeze : 冻结一个类,使其不可修改;
  2. isFrozen : 判断一个类是否已被冻结;
  3. toClass : 通过类加载器加载该CtClass。
  4. prune : 删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用;
  5. defrost : 解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用 prune 方法;
  6. writeFile : 根据CtClass生成 .class 文件;
  7. detach : 将该class从ClassPool中删除;

创建新的方法可以使用CtMethod:

  1. insertBefore : 在方法的起始位置插入代码;
  2. insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
  3. insertAt : 在指定的位置插入代码;
  4. setBody : 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
  5. make : 创建一个新的方法。

JDK,CGLIB

由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了

JDK代理底层实现

代理接口

public interface PrintService {
    void print(String message);
}

代理接口实现类

public class PrintServiceImpl implements PrintService {
    @Override
    public void print(String message) {
        System.out.println("message : "+message);
    }
}

编写Proxy的调用处理程序 InvocationHandler

public class MyInvocationHandler implements InvocationHandler {
    
    private Object proxyTarget;//要代理的真实对象
​
    public MyInvocationHandler(Object proxyTarget) {
        this.proxyTarget = proxyTarget;
    }
​
    //为PrintService 生成代理对象
    public Object getProxy(){
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), proxyTarget.getClass().getInterfaces(), this);
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before proxy invoke ================"+method.getName());
        Object invoke = method.invoke(proxyTarget, args);
        System.out.println("after proxy invoke ================="+method.getName());
        return invoke;
    }
}

Test代码

public static void main(String[] args) {
        PrintServiceImpl printService = new PrintServiceImpl();
        MyInvocationHandler invokeHandler = new MyInvocationHandler(printService);
        PrintService proxy =(PrintService) invokeHandler.getProxy();
​
        System.out.println("proxy class : "+proxy.getClass().getName());
        proxy.print("my proxy test");
    }
/******************************************************************************************/
/**
proxy class : com.sun.proxy.$Proxy0
before proxy invoke ================print
message : my proxy test
after proxy invoke =================print
*/

Proxy类以及接口 InvocationHandler是实现JDK动态代理的核心。看下newProxyInstance

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,    //类加载器loader
                                      Class<?>[] interfaces,  //代理类所实现的接口
                         InvocationHandler h)//当前动态代理对象调用的方法关联InvokerHandler对象
    throws IllegalArgumentException   
{
    .........
    /*
     *省略其他代码,关注下getProxyClass0这个方法
     */
    Class<?> cl = getProxyClass0(loader, intfs);
}
 //getProxyClass0这个方法获取代理类的字节码
     private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        //这里缓存的对象proxyClassCache采用weakReference实现,GC回收会被回收,属于堆外内存
        return proxyClassCache.get(loader, interfaces);
         //看下get方法
    }




public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);
        expungeStaleEntries();
        Object cacheKey = CacheKey.valueOf(key, refQueue);
        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }
        //  着重看下subKeyFactory.apply方法,最终调用ProxyClassFactory.apply
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    }
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
​
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
               
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {          
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

apply首先加载要实现的接口,然后做了一系列配置,接着通过ProxyGenerator.generateProxyClass来生成Class字节码文件,再调用defineClass0(native方法)对其进行加载。

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }
                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }
        return var4;
    }

总结

1、缓存的对象proxyClassCache采用weakReference实现,GC回收会被回收

2、将InvocationHanlder h 作为参数,创建实例对象,所有constructorParams的类型肯定是InvocationHandler.class。

3、最后会调用native关键字的defineClass0这个方法

4、至此整代理类的生成过程已经全部完成,可以看出 JDK生成代理类时是基于接口生成代理类把目标类 当做属性放在代理类里面

CGLIB代理底层实现

代理对象(不需要接口)

public class PrintServiceImpl {
    public void print(String message) {
        System.out.println("message : "+message);
    }
}

编写MethodInterceptor

public class MyMethodInterceptor implements MethodInterceptor {
​
    @Override
    public Object intercept(Object o,  //所生成的代理对象
                            Method method, //要被拦截的方法
                            Object[] objects,  //方法参数
                            MethodProxy methodProxy  //要触发的父类的方法对象(即父类方法代理)
    ) throws Throwable {
        System.out.println("proxy by cglib ....."+method.getName());
        //此处一定要是methodProxy来调用,不然会死循环
        Object res = methodProxy.invokeSuper(o, objects);
        return res;
    }
}

测试

public static void main(String[] args) {
        //将生成的代理class写入到文件夹下
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\Cglib");
        Enhancer enhancer = new Enhancer();
        //设置代理类的对象
        enhancer.setSuperclass(PrintServiceImpl.class);
        //设置代理
        enhancer.setCallback(new MyMethodInterceptor());
​
        PrintServiceImpl printServiceProxy = (PrintServiceImpl)enhancer.create();
        System.out.println(printServiceProxy.getClass().getName());
     
        printServiceProxy.print("print aaa ");
​
    }
/*************************************************************************************/
/**
leonardo.ezio.boot.demo.proxy.PrintServiceImpl$$EnhancerByCGLIB$$f8e5363
proxy by cglib .....print
message : print aaa 
*/

CGLIB原理

create()

public Object create() {
    this.classOnly = false;
    this.argumentTypes = null;
    return this.createHelper();
}
​
private Object createHelper() {
    this.preValidate();
        //private static final Enhancer.EnhancerKey KEY_FACTORY 创建了一个EnhancerKey对象
    Object key = KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID);
    this.currentKey = key;
    //调用父类AbstractClassGenerator方法
    Object result = super.create(key); 
    return result;
}
protected Object create(Object key) {
        try {
            ClassLoader loader = this.getClassLoader();
            Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;
            AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
            if (data == null) {
                Class var5 = AbstractClassGenerator.class;
                synchronized(AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache);
                        data = new AbstractClassGenerator.ClassLoaderData(loader);
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }
​
            this.key = key;
            Object obj = data.get(this, this.getUseCache());
            return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);
        } catch (Error | RuntimeException var9) {
            throw var9;
        } catch (Exception var10) {
            throw new CodeGenerationException(var10);
        }
    }

newInstance

public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
    this.setThreadCallbacks(callbacks);
​
    Object var4;
    try {
        if (this.primaryConstructorArgTypes == argumentTypes || Arrays.equals(this.primaryConstructorArgTypes, argumentTypes)) {
            var4 = ReflectUtils.newInstance(this.primaryConstructor, arguments);
            return var4;
        }
    //最终通过newInstance0反射生成代理对象
        var4 = ReflectUtils.newInstance(this.generatedClass, argumentTypes, arguments);
    } finally {
        this.setThreadCallbacks((Callback[])null);
    }
​
    return var4;
}

通过反射来生成代理对象,阅读源码的过程中我们可以发现cglib生成动态代理的过程中使用了WeakHashMap做为缓存,与jdk中的ProxyClassCache有相似之处,都是采用弱引用实现的

JDK、CGLIB总结

  • JDK基于接口生成代理类 把目标类当做属性放在代理类里面

  • CGLIB基于class生成实现类 继承目标对象加强

  • CGLIB通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,所以final方法无法处理

  • 实现CGLIB动态代理必须实现MethodInterceptor

javassist部分引用,来自https://blog.csdn.net/ShuSheng0007/article/details/81269295

猜你喜欢

转载自blog.csdn.net/weixin_39082432/article/details/105499015
今日推荐