动态代理
2016-05-21 19:49
260 查看
class文件简介以及加载
编译器编译.java -> .class 。JVM虚拟机读取字节码,加载到内存中,生成对应的Class对象。要实现动态代理,最直接的方式就是直接生成字节码,然后加载生成Class对象。运行期代码生成字节码
这个方面有很多开源框架已经帮我们完成了这些功能,比如ASM,Javassist。
ASM
asm的使用操作级别是底层的JVM汇编指令,这要求ASM使用者对class组织结构和JVM汇编指令有一定的了解。下面演示asm的使用:
package samples; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class MyGenerator { public static void main(String[] args) throws IOException { System.out.println(); ClassWriter classWriter = new ClassWriter(0); // 通过visit方法确定类的头部信息 classWriter.visit(Opcodes.V1_7,// java版本 Opcodes.ACC_PUBLIC,// 类修饰符 "Programmer", // 类的全限定名 null, "java/lang/Object", null); //创建构造函数 MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()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....."); 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("D://Programmer.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(data); fout.close(); } }
以上代码生成的字节码等同于以下的效果:
package com.samples; import java.io.PrintStream; public class Programmer { public void code() { System.out.println("I'm a Programmer,Just Coding....."); } }
Javassist
Javassist是一个开源的分析,创建和编辑Java字节码的类库。主要的优点在于简单,快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态的改变类的结构或者生成类。实现上个同样效果的代码如下:import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.CtNewMethod; public class MyGenerator { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); //创建Programmer类 CtClass cc= pool.makeClass("com.samples.Programmer"); //定义code方法 CtMethod method = CtNewMethod.make("public void code(){}", cc); //插入方法代码 method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");"); cc.addMethod(method); //保存生成的字节码 cc.writeFile("d://temp"); } }
运行期加载字节码,生成并使用对象
首先定义一个类加载器package samples; /** * 自定义一个类加载器,用于将字节码转换为class对象 * @author louluan */ public class MyClassLoader extends ClassLoader { public Class<?> defineMyClass( byte[] b, int off, int len) { return super.defineClass(b, off, len); } }
接着载入我们刚生成的代理类字节码
package samples; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; public class MyTest { public static void main(String[] args) throws IOException { //读取本地的class文件内的字节码,转换成字节码数组 File file = new File("."); InputStream input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\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(); } } }
代理
有了上面的知识储备以后,再来理解动态代理。InvocationHandler 是对代理本质的思考。如果只是简单的动态生成代码,实际上通用性太差,以及业务耦合太强。
于是便有了下面的工作方式
要实现这种效果,必然要求Proxy和RealSubject拥有同样的方法。在面向对象编程之中,要实现这种约定有两种方式:
a 定义一个接口,让Proxy和RealSubject都实现这个接口(JDK)
b 通过继承实现(cglib)
JDK动态代理创建机制--接口
1. 获取 RealSubject上的所有接口列表; 2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ; 3. 根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码; 4 . 将对应的字节码转换为对应的class 对象; 5. 创建InvocationHandler 实例handler,用来处理Proxy所有方法调用; 6. Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象
cglib生成动态代理类的机制--继承
1. 查找A上的所有非final 的public类型的方法定义; 2. 将这些方法的定义转换成字节码; 3. 将组成的字节码转换成相应的代理的class对象; 4. 实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的
参考:
http://blog.csdn.net/luanlouis/article/details/24589193相关文章推荐
- Spring_Spring_教程12_Spring利用注解实现Aop
- Linux-磁盘管理-du和df
- GSM网络的结构
- mac 通过vnc连接centos 7
- hdu-5694 BD String(分治)
- 连号区间数
- 奶牛开会 最短路
- Android 解决Listview条目里面有Edittext数据错乱问题
- GDB 使用简单介绍
- dp(第一次尝试)
- 关于java中转意字符\\\\的用法探究
- Spring之Referenced file contains error问题的解决
- Html(二)
- 求阶层的优化
- POJ-2421-Constructing Roads(最小生成树 普利姆)
- HDU 5690 查找循环节 数学公式快速幂+乘法逆元(除法取模)
- MySQL数据库解压缩版(免安装版或zip版)无法输入中文,以及与Navicat中文显示一致的问题
- codeforces 670D1 Magic Powder - 1
- 九度OJ 1001:A+B for Matrices
- 在Android开发中使用MVP模式