Java动态代理机制
/Java动态代理机制主要通过反射机制来实现的,下面讲解一下动态代理相关内容。
代理模式
代理模式是Java中常用的设计模式,主要由公共接口、被代理类和代理类等三部分组成,代理类持有被代理类的实类,代为执行具体的类方法。其中代理类与被代理类有同样的接口。
代理类与被代理类之间通常会存在关联关系,一个代理类的对象与一个被代理类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用被代理类对象的方法来提供特定的服务。
代理模式结构图
使用代理模式的场景
(1)、设计模式中有一个设计原则是开闭原则,指修改关闭对扩展开放,当需要看非本人所写的代码时,通常很难直接修改代码,那么这时就可以通过代理对类进行增强。
(2)、在使用RPC框架时,框架本身并不能提前知道各个业务方要调用哪些接口的哪些方法 。那么这时就可通过动态代理的方式来建立一个中间人给客户端使用,也方便框架进行搭建逻辑,某种程度上也是客户端代码和框架松耦合的一种表现。
(3)、Spring的AOP机制就是采用动态代理的机制来实现切面编程。
静态代理与动态代理
根据加载被代理类的时机不同,将代理分为静态代理和动态代理。
如果在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;如果不能确定,那么可以使用类的动态加载机制,在代码运行期间加载被代理的类,这就是动态代理,比如RPC框架和Spring AOP机制。
静态代理
静态代理:在代码编译时就确定了具体的被代理类。由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
Demo示例
其中被代理类已经明确的在代理类代码中写出,在代码编译时就能确定出该被代理类,从而实现静态代理:
1 | public class test { |
可以看到,代理类ProxyObject在执行被代理类RealObject的方法前后都能够方便地添加新的功能如Spring的面向切面编程AOP,且是通过调用被代理类的方法来实现调用的而非代理类自己直接调用的:
动态代理
基本概念
在代码编译时未确定具体的被代理类,而在代码运行时动态加载被代理的类。在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。在程序运行过程中产生的这个对象,其实就是通过反射机制来生成的一个代理。
注意,JDK提供的代理只能针对接口做代理。
下面来看下实现动态代理机制的Proxy类和InvocationHandler接口。
InvocationHandler接口
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当代理类对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke ()方法来进行调用。
作为InvocationHandler接口唯一的方法,invoke ()方法定义如下:
1 | Object invoke(Object proxy, Method method, Object[] args) throws Throwable |
proxy参数:指代被代理类对象即真实对象;
method参数:指代的是所要调用被代理类对象的某个方法的Method对象;
args参数:指代的是调用被代理类对象某个方法时接受的参数;
Proxy类
Proxy这个类的作用就是用来动态创建一个代理对象类,它提供了许多的方法,其中用的最多的就是 newProxyInstance ()方法:
1 | public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException |
这个方法的作用就是得到一个动态的代理类对象,其中接收三个参数:
loader参数:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理类对象进行加载;
interfaces参数:一个Interface接口对象数组,说明将要给被代理类对象提供一组什么样的接口,如果提供了一组接口给被代理类对象,那么该对象就宣称实现了该接口(多态),这样就能调用这组接口中的方法了;
h参数:一个InvocationHandler对象,表示的是当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上;
创建动态代理对象的具体步骤
1、创建一个InvocationHandler对象
1 | //创建一个与代理对象相关联的InvocationHandler |
2、使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
1 | Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class}); |
3、获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
1 | Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class); |
4、通过构造器constructor来创建一个动态实例stuProxy
1 | Person stuProxy = (Person) cons.newInstance(stuHandler); |
当然,上面四个步骤可以通过Proxy类的newProxyInstances()方法来简化:
1 | //创建一个与代理对象相关联的InvocationHandler |
Demo示例
1 | import java.lang.reflect.InvocationHandler; |
再次提醒,JDK提供的代理只能是针对接口做代理,也就是说调用newProxyInstance()返创建代理类对象时回的必须要是一个接口。
观察输出内容:
第一行内容:”com.sun.proxy.\$Proxy0”是由输出subject.getClass().getName())的结果,我们一般会以为返回的这个代理对象会是Subject类型的对象或者是InvocationHandler的对象,然并非,原因在newProxyInstance()方法的第二个参数上,该参数给代理类对象提供了一组接口,而该对象就会实现这组接口,这时可将这个对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。另外,通过 Proxy.newProxyInstance()创建的代理对象是在JVM运行时动态生成的一个对象,并非是InvocationHandler类型,也不是定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是以$开头、Proxy为中、最后为数字(表示对象的标号)的形式。
第二行至第五行内容(后面4行内容是类似的):”AOP”和”Write Logs”为代理过程中新添加的输出功能;中间的”[*]Send”为代理类对象调用关联的handler中的invoke()方法来执行真实的被代理类的方法;”Method:”输出的是handler中invoke()方法method参数的值,这里可看到为”public abstract void Subject.send()”,说明该方法是来自真实的被代理类对象的接口方法,也就是说,并非是代理类对象直接调用目标方法,而是通过由其关联到的handler对象的invoke()方法中来调用,从而实现代理的方式调用。
同时因为在代码编译时并不知道被代理的类是哪个,因此实现了动态代理的方式。
原理分析
查看Proxy类的newProxyInstance()函数的实现代码:
1 |
|
可以发现只是封装了创建动态代理类的步骤。
重点关注”Class<?> cl = getProxyClass0(loader, intfs);”,其用于产生代理类,后面代码中的构造器也是通过这里产生的类来获得,因此可看出这个类的产生就是整个动态代理的关键。
JDK会生成一个叫$Proxy0的代理类,这个类文件是放在内存中的,在创建代理类对象时,通过反射机制获得这个类的构造方法,然后创建代理类实例。
小结
可以将InvocationHandler接口类看做一个中介类,中介类持有一个被代理对象即真实对象,在invoke()方法中调用了被代理对象相应的方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。