从JDK1.3开始,java就引入了动态代理的概念,动态代理(Dynamic Proxy)可以帮助程序员减少代码的行数,真正提高代码的可复用度。例如,你不必为所有的类的方法里面都写上相同的Log代码行,取而代之的是用类的动态代理。
首先我们来看一下代理模式(Proxy Pattern)
在JDK1.3以前,代理模式就已流行,所有的代理模式是生成一个和类相同接口的代理类,用户通过使用代理类来封装某个实现类。如图1,其目的就是加强实现类的某个方法的功能,而不必改变原有的源代码。
下面我们再来看看动态代理(Dynamic Proxy)
随着Proxy的流行,sun把它纳入到JDK1.3实现了java的动态代理。动态代理和普通代理的区别就是动态代理中的代理类是由java.lang.reflect.Proxy类在运行时根据接口定义,利用java的反射机制动态生成的。和java.lang.reflect.InvocationHandler结合,可以加强现有类的方法实现。如图2:
如上图所示:自定义的Handler实现了InvocationHandler接口,自定义Handler实例化时,将实现类传入自定义的Handler对象(就是把实现类聚合到Handler中)。自定义的Handler实现了InvocationHandler接口要实现其中的invoke()方法,该方法可以使用java反射机制调用实现类的实现方法,同时也可以实现其他功能,例如在调用实现类的方法前加入Log。而Proxy类根据Handler和需要代理的接口动态生成一个接口的实现类的对象。当用户调用这个动态生成的实现类时,实际上就是调用了自定义Handler中的invoke()方法。
下面我们详细分析InvocationHandler和Proxy。
InvocationHandler:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
Java API对它的描述是每一个动态代理类都必须实现InvocationHandler这个接口,并且每个代理类的实例都要关联到某个Handler类中,当我们通过代理对象调用一个方法时,这个方法的调用就会转化为由InvocationHandler这个接口的invoke()方法进行调用,下面我们来看看InvocationHandler接口中唯一一个方法invoke():
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
这个方法一共有三个参数,下面我们来看一下这三个参数分别代表什么意思?
Object invoke(Object proxy, Method method, Object[] args) throws Throwable proxy: 指代我们所代理的那个真实对象 method: 指代的是我们所要调用真实对象的某个方法的Method对象 args: 指代的是调用真实对象某个方法时接受的参数
先不着急,等下通过一个案例来进行更深入的讲解。
接下来我们看看Proxy这个类的描述:
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了很多方法,但是我们用的最多的就是newProxyInstance()方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
这个方法的作用就是得到一个动态的代理对象,其接受三个参数,我们来看看这三个参数的含义:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载 interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了 h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
下面我们通过一个案例来体会动态代理:
首先我们定义了一个Subject类型的接口,为其声明了两个方法:
public interface Subject { public void rent(); public void hello(String str); }
接着,我们定义了一个类去实现这个接口 ,这个类就是我们的真实对象,RealSubject类:
/** * 真实对象 * @author lavimer * time : 2015年1月5日下午4:44:37 * @version */ public class RealSubject implements Subject { @Override public void rent() { System.out.println("I want to rent my house!"); } @Override public void hello(String str) { System.out.println("hello:" + str); } }
下一步,我们就要定义一个动态代理类,前面说过,每一个动态代理类都必须实现InvocationHandler这个接口,因此我们定义的这个动态代理类也不例外:
/** * 自定义的动态代理类必须要实现InvocationHandler接口 * @author lavimer * time : 2015年1月5日下午4:46:15 * @version */ public class MyDynamicProxy implements InvocationHandler { //把要代理的真实对象关联到动态代理类中 private Object subject; //通过构造方法将要代理的真实对象传进来 public MyDynamicProxy(Object subject) { this.subject = subject; } @Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { //在代理真实的对象前,我们可以添加一些自己的操作 System.out.println("Before rent house"); System.out.println("Method:" + method); //当代理对象调用真实对象的方法时,其会自动跳转到代理对象关联的handler对象的invoke()方法来进行调用 method.invoke(subject, args); //在代理真实对象后我们也可以添加一些自己的操作 System.out.println("after rent house"); return null; } }
最后,来看看我们的Client类:
public class Client { public static void main(String[] args) { //创建我们要代理的真实对象 Subject reSubject = new RealSubject(); //我们要代理哪个对象,就将该对象传进去,最后时通过该真实对象来调用其方法 InvocationHandler handler = new MyDynamicProxy(reSubject); /** * 通过Proxy的newProxyInstance()方法来创建我们的代理对象,我们看看它的三个参数 * 第一个参数:reSubject.getClass().getClassLoader(),我们这里使用reSubject这个类的ClassLoader对象来加载我们的代理对象 * 第二个参数:realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实现的接口,那么这个代理对象就宣称实现了该接口,从而就能调用这组接口中的方法了。 * 第三个参数:handler,表示的是代理对象调用方法时会关联到handler这个对象上。 */ Subject subject = (Subject) Proxy.newProxyInstance(reSubject.getClass().getClassLoader(), reSubject.getClass().getInterfaces(), handler); System.out.println(subject.getClass().getName()); //代理对象调用方法 subject.rent(); subject.hello("java"); } }
观察控制台的输出如下:
com.sun.proxy.$Proxy0 Before rent house Method:public abstract void com.lixue.proxy.Subject.rent() I want to rent my house! after rent house Before rent house Method:public abstract void com.lixue.proxy.Subject.hello(java.lang.String) hello:java after rent house
我们先来看看com.sun.proxy.$Proxy0这个东西,我们看到,这个东西是由subject.getClass().getName()产生的,也就是说关键还在于subject这个对象,获取subject的语句如下:
Subject subject = (Subject)Proxy.newProxyInstance(reSubject.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler);
为什么我们得到的subject不是Subject类型的对象,或者InvocationHandler类型的对象呢?我先解释一下这里为什么要强制转化为Subject类型的对象吧,原因就是在newProxyInstance()这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我们这个代理对象就会实例化这组接口,这个时候我们当然可以将这个代理对象强制转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。
同时我们一定要记住,通过Proxy.newProxyInstance()创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是这样的形式即以$开头,proxy在中间,最后一个数字表示对象的标号。
说完这个问题之后,我们来看看这两句:
subject.rent(); subject.hello("java")
这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的handler中的invoke()方法去执行,而我们的这个handler对象又接受了一个RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用handler中的invoke()方法去执行:
@Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { //在代理真实的对象前,我们可以添加一些自己的操作 System.out.println("Before rent house"); System.out.println("Method:" + method); //当代理对象调用真实对象的方法时,其会自动跳转到代理对象关联的handler对象的invoke()方法来进行调用 method.invoke(subject, args); //在代理真实对象后我们也可以添加一些自己的操作 System.out.println("after rent house"); return null; }
我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在方法前后添加一些自己的操作,
public abstract void com.xiaoluo.dynamicproxy.Subject.rent() public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
正好就是我们的Subject接口中的两个方法,这也就证明了当我们通过代理对象来调用方法的时候,实际就是委托由其关联到的handler对象的invoke()方法中来调用,并不是自己真实调用,而是通过代理的方式来调用。
收工!!!
文章来自: