一个人静静坐在电脑面前写代码的感觉,那是什么感觉?那是武林高手闭关修炼的感觉。
spring-core项目结构如下:
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通过绕开对象实例构造器来克服这个限制。常见使用场景有:
- 序列化,远程调用和持久化 -对象需要实例化并存储为到一个特殊的状态,而没有调用代码
- 代理,AOP库和Mock对象 -类可以被子类继承而子类不用担心父类的构造器
- 容器框架 -对象可以以非标准的方式被动态实例化
asm 类关系图
asm 核心
asm使用访问者模式实现属性、方法、类等的修改,常见访问者有:
- ClassVistor
- ModuleVistor
- MethodVistor
- FieldVistor
- AnnotationVisitor
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 通过派生子类的方式实现代理,而派生子类的核心是
-
ClassGenerator。
cglib demo
cglib代理实现核心类:
- Enhancer 通过ClassGenerator生成需要代理类的子类。
@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
通过代理可以看到当前实例对象的类名包括 字符串,一般包括类似字符串的都是动态生成的子类。
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。
经典技术,无限可能。