动态代理

动态代理

相比于静态代理,动态代理可以避免编写各个繁锁的静态代理类,只需简单地指定一组接口及目标类对象就能动态的获得代理对象实例。

JDK动态代理

JDK从1.3版本已经内置对动态代理实现的支持,我们可以通过java.lang.reflect.Proxy类以及java.lang.reflect包中的InvocationHandler接口来自己实现动态代理。

hello world

接下来,编写一个动态代理的”hello world”:

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
/**
* 定义被代理的接口
*/
interface Hello{
void sayhello();
}
/**
* 接口的实现类
*/
class helloImpl implements Hello {
@Override
public void sayhello() {
System.out.println("hello world");
}
}
/**
* 调用处理程序(InvocationHandler)
* 在最终生成的代理类中包含一个InvocationHandler实现类的成员变量(该成员变量继承自Proxy类)。
* 在代理类实例调用代理的方法时,将对方法调用进行编码(Method变量)并将其指派到它的调用处理程序的invoke方法。
* 所以对被代理方法的调用都是通过InvocationHadler的invoke来实现的。
*/
class MyInvocationHandler implements InvocationHandler {
//目标对象,被代理接口的实现类
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
/**
* @param proxy 代理类实例
* @param method 方法
* @param args  方法入参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------织入前置增强代码-------------");
//执行相应的目标方法
Object rs = method.invoke(target,args);
System.out.println("------织入后置增强代码-------------");
return rs;
}
}
public class test {
public static void main(String[] args) throws Exception {
//获取动态代理类,生成具体的代理类字节码
Class proxyClazz = Proxy.getProxyClass(Hello.class.getClassLoader(),Hello.class);
//通过反射获得代理类的构造函数,并传入参数类型InvocationHandler.class
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
//通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
Hello iHello = (Hello) constructor.newInstance(new MyInvocationHandler(new helloImpl()));
//通过代理对象调用目标方法
iHello.sayhello();
//上述生成代理类实例的代码,JDK提供了一个统一的方法Proxy.newProxyInstance()完成。传入参数:类加载器,目标对象实现的接口,InvocationHandler的实现类
Hello myHello = (Hello) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new helloImpl().getClass().getInterfaces(), new MyInvocationHandler(new helloImpl()));
//通过代理对象调用目标方法
myHello.sayhello();
//toString(),hashCode(),equals()也被代理了,都织入了增强逻辑。通过反编译代理类可以看到静态块中初始化了这些method
// iHello.toString();
// iHello.hashCode();
// iHello.equals(new Object());
}
}

代码解析:
上述方法中,我们通过java.lang.reflect.Proxy实现了动态代理。如果我们反编译动态生成的代理类实例,我们会发现,代理类实例iHello或者myHello实际上继承了Proxy同时实现了Hello接口(由于Java是单继承语法,所以JDK提供的动态代理只能代理接口方法)。代理类实例iHello或者myHello在内部调用被代理的接口方法sayhello()时,是通过调用在代理类的构造方法中传入的new MyInvocationHandler(new helloImpl())的invoke()方法实现的。

另外,在上述例子中我们在InvocationHandler中写的增强逻辑不仅仅增强了被代理的接口方法,还
增强了来自Object的三个方法toString(),hashCode(),equals()。在上述代码末尾注释掉的三行代码的输出可以证实这三个方法确实也织入了增强逻辑。

总结

我们已知需要被代理的接口方法,以及对应的接口和实现了接口方法的目标对象(被代理对象),如何通过JDK实现动态代理?

1.通过实现InvocationHandler接口来自定义自己的InvocationHandler;在实现的过程中,通过传入目标对象(被代理的对象)来辅助完成invoke()方法,同时可以在invoke方法中织入增强逻辑。

2.通过Proxy.getProxyClass获得动态代理类,该类继承了java.lang.reflect.Proxy,同时实现了被代理方法的接口(动态生成代理类的字节码)

3.通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)

4.通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入,作为代理对象的成员变量来使用

5.通过代理对象调用目标方法,实际是通过上述InvocationHandler实现类中的invoke()方法调用

CGLIB动态代理

动态代理要求的目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理(CODE GENERLIZE LIBRARY),也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类。对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。

JDK动态代理和CGLIB代理生成的区别

  1. JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

  2. CGLIB动态代理是利用ASM开源包,将目标对象类的class文件加载进来,通过修改其字节码生成子类来处理。CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。

  3. JDK动态代理是面向接口的,在创建代理实现类时比CGLIB要快,创建代理速度快。CGLIB动态代理是通过字节码底层继承要代理类的目标类来实现,创建速度没有JDK动态代理快,但是运行速度比JDK动态代理快。

在Spring AOP中,两种代理技术都有使用,如果目标对象是接口实现类,那么Spring采用JDK动态代理来完成,如果目标对象不是接口实现类,Spring会使用CGLIB来实现动态代理。当然,也可以通过配置文件强制使用CGLIB动态代理。因为在Spring容器中,大多数bean是单例的,所以只创建一次,所以推荐使用CGLIB来代理。

0%