Java技能图谱

3.srping5源码分析-asm、cglib、objenesi

2020-03-12  本文已影响0人  求索

一个人静静坐在电脑面前写代码的感觉,那是什么感觉?那是武林高手闭关修炼的感觉。

spring-core项目结构如下:


3.springcore项目结构.png

spring-core 主要包括asm、cglib、objenesis、core、lang、util一共6个顶层包。

asm、cglib、objenesis介绍

asm是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

cglib是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。

objenesis是一个小型类库用来实例化一个特定class的对象, 在类库中经常会有类必须拥有一个默认构造器的限制。Objenesis通过绕开对象实例构造器来克服这个限制。常见使用场景有:

  1. 序列化,远程调用和持久化 -对象需要实例化并存储为到一个特殊的状态,而没有调用代码
  2. 代理,AOP库和Mock对象 -类可以被子类继承而子类不用担心父类的构造器
  3. 容器框架 -对象可以以非标准的方式被动态实例化

asm 类关系图

2.asm类关系图.png

asm 核心

asm使用访问者模式实现属性、方法、类等的修改,常见访问者有:

asm使用demo

@Test
public void testCreateClass() throws IOException {
    //java 1.8 类修改
    ClassWriter writer = new ClassWriter(0);
    writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com.demo.AsmDemo", null, null, new String[]{"java.lang.Runnable"});

    // 添加方法:修饰符、方法名、描述符、签名、抛出的异常
    MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "run",
            "()V", null, null);
    //获取静态属性, 输出 System.out
    // Opcodes: 当前操作类型,ower:当前属性归属的类,name:当前属性名称 descriptor:当前属性类描述
    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    //加载变量
    mv.visitLdcInsn("Hello ASM!");
    // 调用方法
    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);
    // 返回
    mv.visitInsn(Opcodes.RETURN);
    // 设置栈大小和局部变量表大小
    mv.visitMaxs(2, 1);
    // 方法结束
    mv.visitEnd();
    // 类完成
    writer.visitEnd();
    // 生成字节数组,并输出
    byte[] result = writer.toByteArray();
    String filePath = "E:\\study\\review\\basic\\demo-console\\doc\\AsmDemo.class";
    FileOutputStream outputStream = new FileOutputStream(filePath);
    outputStream.write(result);
    outputStream.flush();
    outputStream.close();

}

生成的 .class 文件如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class com.demo.AsmDemo implements Runnable {
    public void run() {
        System.out.println("Hello ASM!");
    }
}

-修改类
和创建一个类主要区别在与 classReader.accept(writer,ClassReader.EXPAND_FRAMES),用于将ClassWriter修改的内容扩展到旧代码里面去。

@Test
public void testChangedClass() throws IOException {
    String filePath = "E:\\study\\review\\basic\\demo-console\\doc\\AsmDemo.class";
    ClassReader classReader = new ClassReader(new FileInputStream(filePath));
    ClassWriter writer = new ClassWriter(classReader,ClassWriter.COMPUTE_MAXS);
    //通过ClassReader.EXPAND_FRAMES 参数告知扩展类,而不是替换现有类
    classReader.accept(writer,ClassReader.EXPAND_FRAMES);
    MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "excute",
            "()V", null, null);
    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    mv.visitLdcInsn("Add a excute methods!");
    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);
    mv.visitInsn(Opcodes.RETURN);
    mv.visitMaxs(2, 1);
    mv.visitEnd();
    writer.visitEnd();
    byte[] result = writer.toByteArray();
    String filePath2 = "E:\\study\\review\\basic\\demo-console\\doc\\AsmDemo.class";
    FileOutputStream outputStream = new FileOutputStream(filePath2);
    outputStream.write(result);
    outputStream.flush();
    outputStream.close();
}

输出类反编译显示:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class com.demo.AsmDemo implements Runnable {
    public void run() {
        System.out.println("Hello ASM!");
    }

    public void excute() {
        System.out.println("Add a excute methods!");
    }
}

ASM 通过读取字节代码修改类,要准确的修改,需要熟悉字节码修改规范,名称不同类型在编译时是怎么写的,命令是什么。

cglib

cglib 通过派生子类的方式实现代理,而派生子类的核心是

cglib demo

cglib代理实现核心类:

@Test
public void testProxy(){
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(HelloCglib.class);
    enhancer.setCallback((MethodInterceptor)
            (obj, method, args1, proxy) -> {
        System.out.println("className:"+obj.getClass().getSimpleName());
        Object result = proxy.invokeSuper(obj, args1);
        return result;
    });
    HelloCglib sample = (HelloCglib) enhancer.create();
    sample.hello();
}

输出:

className:HelloCglib$$EnhancerByCGLIB$$e56e437a
hello cglib

通过代理可以看到当前实例对象的类名包括 EnhancerByCGLIB 字符串,一般包括类似字符串的都是动态生成的子类。

objenesis

看一个没有默认构造函数的类

public class HelloObjenesis {
    private String hello="Objenesis!";
    public HelloObjenesis(String hello){
        this.hello=hello;
    }
    public void  hello(){
          System.out.println("你好! "+hello);
    }
}

正常情况下,HelloObjenesis实例化必需传入 hello参数,通过objenesis 可以忽略构造函数,直接创建对象。

   @Test
    public void testObjenesis(){
        Objenesis objenesis = new ObjenesisStd(true);
        HelloObjenesis test = objenesis.newInstance(HelloObjenesis.class);
        test.hello();
    }

输出:

 你好! null

总结

为了动态修改或扩展类引入了asm;引入asm后又想在方法调用前后插入日志、事务等操作,所以引入cglib;有了cglib后遇到了无默认构造函数的类,所以引入了objenesis。

经典技术,无限可能。

上一篇 下一篇

猜你喜欢

热点阅读