据我所以所指,目前有JDK动态代理,javassist,cglib,asm,实现方式不尽相同,但是原理相同。
2:动态代理实现原理分析正常的开发流程是,编译器编译java源代码生成.class的字节码文件,但是需要注意,.class其实也是一种符合某种规范的文件,只不过并不是人类可读的文件,而是一种二进制文件,那么这种文件谁能够解析呢,JVM,JVM能够加载这些文件,经过各种各系工作之后,生成Class对象,其中执行加载工作的是类加载器抽象类java.lang.ClassLoader,详细的过程如下图:
那么动态代理在哪里发挥作用呢?答案是在运行期动态的生成符合JVM规范的class文件,执行重新加载的工作,比如动态创建某个已加载的类的子类的class字节码文件,然后在执行加载的工作,此时这个过程如下图:
这就是动态打理的原理,当拥有了这样的动态编写代码的能力之后,任何代码我们都能够动态生成了,功能就会变得相当强大了。
下面我们来看一个使用类加载器加载的例子。
- 源代码
package yudaosourcecode.huohuo;
public class Programmer {
public void code() {
System.out.println("I'm a Programmer,Just Coding.....");
}
}
2.2:定义类加载器
- 源码
package yudaosourcecode.huohuo;
public class MyClassLoader extends ClassLoader {
public Class> defineMyClass( byte[] b, int off, int len) {
return super.defineClass(b, off, len);
}
}
2.3:定义测试类
该类使用2.2:定义类加载器中定义的类加载器加载2.1:定义待加载的类的.class文件到JVM中生成Class对象,然后使用Class对象创建实例对象。
package yudaosourcecode.huohuo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
public class MyTest {
public static void main(String[] args) throws Exception {
//读取本地的class文件内的字节码,转换成字节码数组
File file = new File(".");
InputStream input =
new FileInputStream("/Users/xb/Desktop/D/dongsir-dev/java-life-current/java-life/target/classes/yudaosourcecode/huohuo/Programmer.class");
byte[] result = new byte[1024];
int count = input.read(result);
// 使用自定义的类加载器将 byte字节码数组转换为对应的class对象
MyClassLoader loader = new MyClassLoader();
Class clazz = loader.defineMyClass(result, 0, count);
//测试加载是否成功,打印class 对象的名称
System.out.println(clazz.getCanonicalName());
//实例化一个Programmer对象
Object o = clazz.newInstance();
try {
//调用Programmer的code方法
clazz.getMethod("code", null).invoke(o, null);
} catch (IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
}
运行:
yudaosourcecode.huohuo.Programmer I'm a Programmer,Just Coding.....
可以看到执行成功了,动态代理的执行过程也大概如此,只不过,加载的并不是磁盘中现成的class文件,而是在代码中动态生成字节码文件,然后使用类加载器执行动态加载。而当前Java中有些框架就提供了了这样的能力,如asm,javasist等。
3:asmasm对于使用者的要求较高,需要熟悉字节码文件的格式,了解字节码相关的指令,如如何设置版本号,描述方法签名,类签名等,都必须和字节码文件一一对应。如下是是用asm来实现如下的程序对应的class字节码文件:
package yudaosourcecode.huohuo;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyGenerator {
public static void main(String[] args) throws IOException {
System.out.println();
ClassWriter classWriter = new ClassWriter(0);
// 通过visit方法确定类的头部信息
classWriter.visit(Opcodes.V1_8,// java版本
Opcodes.ACC_PUBLIC,// 类修饰符
"Programmer", // 类的全限定名
null, "java/lang/Object", null);
//创建构造函数
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "","()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 定义code方法
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
null, null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....by asm!!!");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
// 使classWriter类已经完成
// 将classWriter转换成字节数组写到文件里面去
byte[] data = classWriter.toByteArray();
File file = new File("/Users/xb/Desktop/temp/mycls/Programmer.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}
运行生成文件如下:
使用jd-gui查看生成的字节码内容如下:
然后使用2.3:定义测试类中的类进行测试(注意修改字节码文件路径),输出结果如下:
Programmer I'm a Programmer,Just Coding.....by asm!!!4:javassist
如果说asm是面向字节码来动态生成字节码文件的话,那么javassit就是面向java源代码来生成字节码文件的,因此javassit的难度相对于和asm要小的多,复杂度低得多,编程效率高得多,下面使用javassit来动态生成一个字节码文件:
package yudaosourcecode.huohuo;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class MyGeneratorByJavassit {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建Programmer类
CtClass cc= pool.makeClass("com.samples.ProgrammerJavassit");
//定义code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代码
method.insertBefore("System.out.println("I'm a Programmer,Just Coding.....by javassit!!!");");
cc.addMethod(method);
//保存生成的字节码
cc.writeFile("/Users/xb/Desktop/temp/mycls");
}
}
生成字节码如下:
然后使用2.3:定义测试类中的类进行测试(注意修改字节码文件路径),输出结果如下:
com.samples.ProgrammerJavassit I'm a Programmer,Just Coding.....by javassit!!!



