您的位置:首页 > 其它

动态代理

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: