[toc]
动态代理
Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:
InvocationHandler:该接口中仅定义了一个invoke方法。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
1
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
|
第一个参数obj一般是指要代理的类,method是被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
Proxy:该类的作用就是用来动态创建一个代理对象的类,其中主要包含以下方法:
1 2 3 4 5 6 7
| //获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组. public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException{…}
//返回代理类的一个实例,返回后的代理类可以当作被代理类使用. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{…}
|
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载。
interfaces: 一个Interface对象的数组,表示的是要给需要代理的对象提供一组什么接口,如果提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样就能调用这组接口中的方法了。
h: 一个InvocationHandler对象,表示的是当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。
Proxy.newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)做了以下几件事.
(1)根据参数 loader 和 interfaces 调用 getProxyClass(loader, interfaces) 创建代理类$Proxy0. $Proxy0类实现了interfaces的接口,并继承了Proxy类.
(2)实例化 $Proxy0 并在构造方法中把loader传过去, 接着 $Proxy0 调用父类Proxy的构造器,为h赋值。接着把得到的 $Proxy0 实例强制转换成代理类. 当执行代理类的方法时,就调用了 $Proxy0 类中的方法.在执行代理类方法中,调用父类Proxy中的h的invoke()方法.即InvocationHandler.invoke()。
示例:
1 2 3 4 5
| package com.zero;
public interface IHello { void say(); }
|
1 2 3 4 5 6 7 8 9 10
| package com.zero;
public class HelloImpl implements IHello {
@Override public void say() { // TODO Auto-generated method stub System.out.println("Say hello!"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.zero;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;
public class JDKProxyHandler implements InvocationHandler { // 这个就是我们要代理的真实对象 private Object realObject;
// 构造方法,给我们要代理的真实对象赋初值 public JDKProxyHandler(Object target) { this.realObject = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("jdk...invoke() begin"); System.out.println(method); Object result = method.invoke(realObject, args); System.out.println("jdk...invoke() end"); return result; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.zero;
import java.lang.reflect.Proxy;
public class JDKProxyTest {
public static void main(String[] args) { // TODO Auto-generated method stub HelloImpl impl = new HelloImpl(); // 这里把handler与impl新生成的代理类相关联 JDKProxyHandler handler = new JDKProxyHandler(impl); // 获得代理类($Proxy0 extends Proxy implements IHello)的实例. IHello hello = (IHello) Proxy.newProxyInstance(impl.getClass() .getClassLoader(), impl.getClass().getInterfaces(), handler); // 这里无论访问哪个方法,都是会把请求转发到handler.invoke hello.say(); System.out.println(hello.getClass()); //class com.sun.proxy.$Proxy0 } }
|
JDK的动态代理机制只能代理实现了接口的类,而没有实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.zero.jdk_cglib;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGlibProxyHander implements MethodInterceptor { private Object target; // target不必须使用接口 public Object createProxyInstance(Object target) { this.target = target;
Enhancer enhancer = new Enhancer(); // 实际上是继承了目标类,覆盖目标类所有非final方法,再基础上添加新的代码 // 使用Enhancer可以对任意类生成代理类, // 只要enhancer.setSuperclass(this.target.getClass()); // 其实质是代理类是目标类的子类。 enhancer.setSuperclass(this.target.getClass());
// 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截 enhancer.setCallback(this); return enhancer.create(); }
// 从参数构成上,intercept的输入参数比invoke多1个,其实前3个参数对象的含义与invoke的含义是相同的 // 第一个参数表示调用方法来自哪个对象; // 第二个参数表示调用方法的Method对象; // 第三个参数表示此次调用的输入参数列表; // 多出来的参数是 MethodProxy 类型的,它应该是cglib生成用来代替Method对象的一个对象, // 使用MethodProxy比调用JDK自身的Method直接执行方法效率会有提升 @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object result = null; System.out.println("CGlib代理 " + method.getName() + "()方法"); result = methodProxy.invoke(this.target, args); return result; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.zero;
import java.lang.reflect.Proxy;
public class CGlibProxyTest {
public static void main(String[] args) { // TODO Auto-generated method stub HelloImpl impl = new HelloImpl(); CGlibProxyHander hander = new CGlibProxyHander(); IHello hello = (IHello) hander.createProxyInstance(impl); hello.say(); System.out.println(hello.getClass()); // class com.sun.proxy.$Proxy0 } }
|
除了上面介绍的两种代理方式,还有一个方式是byte buddy,是基于ASM实现。mockito其核心就是基于byte buddy实现的,可以动态生成mock类。
上面都是使用代理的方式做类增强。由于JVM通过字节码的二进制信息加载类的,在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。再把这个二进制数据加载转换成对应的类,这样也可以完成动态代理,做到类增强。当前有很多开源框架可以完成这些功能,如ASM,Javassist。
ASM
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。
下面通过ASM 生成下面类Programmer的class字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes;
import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException;
public class AsmGenerator {
public static void main(String[] args) throws Exception { Class clazz = new ClassLoader() { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] b = classBytes(); return defineClass(name, b, 0, b.length); } }.findClass(null);
//测试加载是否成功,打印class 对象的名称 System.out.println(clazz.getCanonicalName());
//实例化一个Programmer对象 Object o = clazz.newInstance(); clazz.getMethod("code", null).invoke(o, null); }
private static byte[] classBytes() { 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, "<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(); return data; } /** * public class Programmer { * public void code() { * System.out.println("I'm a Programmer,Just Coding....."); * } * } */
}
|
Javassist
Javassist是一个开源的分析、编辑和创建Java字节码的类库。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
下面通过Javassist创建上述的Programmer类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.zero.jdk_cglib.javassist;
import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.CtNewMethod;
public class AssistGenerator { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); //创建Programmer类 CtClass cc= pool.makeClass("com.zero.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");
Class clazz = cc.toClass(); //测试加载是否成功,打印class 对象的名称 System.out.println(clazz.getCanonicalName()); //实例化一个Programmer对象 Object o = clazz.newInstance(); clazz.getMethod("code", null).invoke(o, null); } }
|