1 为什么要用动态代理
上一次我们详细分析了静态代理模式的原理,并且用代码简单实现了一个静态代理的案例。但是我们会发现在静态代理中代理类与被代理类都需要实现同一个接口,这就说明我们的一个静态代理类只能代理一个类,并且还要事先知道我们要代理哪个类才能写代理类,如果我们有其他类还想使用代理那就必须再写一个代理类。然而在实际开发中我们是可能是有非常多的类是需要被代理的,并且事先我们可能并不知道我们要代理哪个类。所以如果继续使用静态代理反而会增加许多的工作量,并且效率低下,代码复用率也不好。由此我们JDK为我们提供了动态代理这个概念,即利用一个代理类就可以实现所有被代理的操作。下面我们就来详细分析动态代理到底是怎么一回事。
2 动态代理
2.1什么是动态代理
之前我们了解到静态代理指代理类在程序运行前就已经存在。那么反之动态代理的代理类在的程序运行前是不存在的,也就是说代理类在程序运行时才创建的代理模式成为动态代理。这种情况下,代理类并不是在Java代码中定义好的,而是在程序运行时根据我们的在Java代码中的“指示”动态生成的。这么说比较抽象,我们先通过一个案例来看动态代理到底怎么实现的。
2.2.动态代理简单实现
还是我们之前那个实例:一个班上的同学要向老师提交作业,但是老师并不直接收每个人的作业,都是通过把作业交给学习委员,再由学习委员将作业转交给老师。我们今天用动态代理的方式来将它实现。我们沿用之前写好的Person接口与Student类,具体代码实现请参考上一篇博文《Java代理模式实现与详解(一)》
我们先来写一个代理类MyProxy,通过这个类实现动态代理。代码如下:
大家可以看到,这个类实现了一个接口 InvocationHandler,还引入了泛型,并且重写了invoke()方法,在invoke()方法里我们打印了一句话记录我们具体代理执行哪个方法。那为什么要这么做呢?我们下面会对其进行详细的介绍。
代理类我们已经写好了,那么我们来看看怎么实现动态代理呢。编写一个测试类Test.java 用于测试我们的动态代理。
运行结果:
从代码中我们可以看到,我们先是创建一个被代理的对象,然后创建一个InvocationHandler对象handler并且将要代理的对象与其绑定。接着我们创建了一个代理对象stuProxy,并将handler作为参数传了去,最终通过stuProxy代理执行了submitHomework方法,完成了代理。
在代理类中我们并没有声明我们要代理的类具体是哪个而是在我们的测试类中,也就是我们需要用的时候才创建我们想要代理的被代理对象,并将其传进代理类中,最终完成了代理,这就是动态代理模式。
在代理类中我们实现了一个InvocationHandler接口,在测试类中我们调用了一个Proxy类,由此我们才完成了动态代理功能,那为什么我们要这么做呢?他们又代表着什么呢?我们接下来详细的分析其原理。
3 动态代理原理分析
3.1.InvocationHandler接口
如果想要实现动态代理模式,那么首先必须要观察一个接口:
java.lang.reflect.InvocationHandler;我们进到这个接口的源码去观察:
在这个接口里面我们可以看到它只有一个invoke()方法。那这个invoke()方法有什么作用呢?这个方法里面有三个参数,那这三个参数又分别代表什么?我们来看它里面的注释:
这个接口里面对三个参数的作用做了详细的解释。我们总结一下:
Object proxy:表示代理类的对象。
Method method:表示正在调用的方法。
Object[] args:表示接口方法里面接收的参数,如果接口方法不使用参数则为null。
我们再来看他的返回值类型是什么:
通过给出的注释我们可以知道此方法返回的值一定是相应基本包装对象类的实例。也就是返回我们的代理对象,所以这个方法就属于代理类中调用真实主题类即被代理类的操作方法。但是我们可以发现这个方法里没有所对应的被代理对象,所以在创建这个类对象的时候设置好真实的操作对象。那我们又怎么去进行设置呢?其实在上面的案例中我们已经做出了示范操作。我们要想找到代理对象则要使用java.lang.reflect.Proxy类进行动态创建。这个类是怎么完成创建的呢?我们根据其源码来详细分析。
3.2. java.lang.reflect.Proxy类
在这个类中我们主要关注一个方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException ; 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。正是此方法实现了我们的动态代理功能。我们先来看一下此方法里的参数:
ClassLoader loader:指的取得对象的加载器。
Class<?>[] interfaces:代理设计模式的核心是围绕接口进行的,我们必须取出全部的接口。所以这个参数指代理类要实现的接口列表。
InvocationHandler h:指派方法调用的调用处理程序即代理的实现类。
我们来看这个方法具体的实现代码:
我们先来分析上面的部分代码,代码中先检查了代理的实现类对象是否为空,是的话就抛出NullPointerException,接着对传入的接口进行安全检查。我们继续来看代码:
这里我们根据给出的注释我们便能明白其作用,作用是查找或者生成指定的代理类,这段代码便是实现动态代理的核心方法,动态代理的思路其实就是生成一个新类。
我们接着往下看:
同样的我们先看代码注释,意为:使用指定的调用处理程序调用其构造函数,即一个指定接口的代理类实例。至此我们便能明白我们为什么要用此方法区设置我们上面所说的真实的操作对象。我们返回来看之前我们写的案例代码:
我们正是通过我们上面所说的Proxy.newProxyInstance方法,设置真实的操作对象,完成动态代理。方法的第一个参数我们通过类加载器取得我们要代理的对象,第二个参数我们取得了代理类要实现的接口列表也就是Person这个类里面的接口列表,最后第三个参数我们将代理的实现类对象传了进去。通过此方法我们获得了一个Person的代理对象。最终通过代理对象完成了代理操作。
4 总结
本文对Java动态代理做了详细的分析,并实现了一个简单的动态代理案例,实现动态代理最关键的就是一个接口InvocationHandler和一个类Proxy。上面动态代理的例子,其实就是一个AOP的简单实现了。Spring的AOP实现也用到了Proxy和InvocationHandler这两个东西。但是我们从Proxy类可以发现,本文我们分析的Java动态代理只能对接口实现代理,无法实现对class的动态代理。那是否意味着如果没有接口我们就不能使用动态代理模式了呢?有没有什么代理方式能不依赖接口进行代理呢?