Java动态代理机制详解(JDK和CGLIB、Javassist、ASM)

[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);
}
}