代理模式是一种常见的设计模式,目的就是提供一个代理控制对目标对象的访问,在被代理对象方法执行前后进行一些处理。本篇主要探究JDK自带的动态代理实现。
1.代理模式
代理模式的UML图如下:
通常代理类和被代理类会实现相同接口,对于Client来说没有任何差别,在代理类内部调用了被代理类的方法,来实现代理的操作。
2.Proxy类
Java的Proxy类实现了代理模式,我们通过JavaApi文档中的例子来理解Proxy是如何实现代理的
InvocationHandler handler = new MyInvocationHandler(...);
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
newInstance(handler);
可以看到Jdk实现动态代理的方式,步骤如下:
创建InvocationHandler实现代理逻辑
根据被代理类的类加载器和接口创建获取代理类
通过包含InvocationHandler构造函数创建代理类实例
Proxy类将这三步进行了包装,newProxyInstance()方法就是做这件事情,这使得使用动态代理变得非常容易。
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class<?>[] { Foo.class },
handler);
看下InvocationHandler类的注释
/**
* Processes a method invocation on a proxy instance and returns
* the result. This method will be invoked on an invocation handler
* when a method is invoked on a proxy instance that it is
* associated with.
*/
//执行一个委托给代理实例的方法并返回结果。当绑定的proxy实例调用方法时,将会调用InvocationHandler的这个方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
3.示例
下面我们通过一个示例展示如何使用动态代理。
首先,新建一个接口Bird
public interface Bird {
void fly();
}
其次,建立一个实现类Pigeon,我们实现一个代理,这个代理是对任意Bird的子类,都能增加“唱歌”的功能,如下
public class Pigeon implements Bird {
@Override
public void fly() {
System.out.println("信鸽飞行");
}
public static void main(String[] args) {
Bird bird = new Pigeon();
Bird proxy = (Bird) Proxy.newProxyInstance(bird.getClass().getClassLoader(), bird.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass().getName());
System.out.println("唱歌");
method.invoke(bird, args);
return null;
}
});
System.out.println("------");
proxy.fly();
System.out.println("------");
}
}
运行main,结果如下:
------
com.sun.proxy.$Proxy0
唱歌
信鸽飞行
------
4.Proxy类的真相
上述结果中我打印了生成的代理类的类名,如上是com.sun.proxy.$Proxy0
,通过查看Proxy类生成的代理类具体是什么,能更容易理解InvocationHandler的调用时机。因为代理类是运行时生成的(通过反射),我们借助工具来获取这个代理类的源码。
我们在main函数前加上一句配置
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
同时在IDEA中安装反编译工具Java Bytecode Decompiler
,执行main函数后,可以看到新生成了一个文件,打开如下:
public final class $Proxy0 extends Proxy implements Bird {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
//注入InvocationHandler的构造函数
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//方法代理
public final void fly() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//省略无用代码
}
可以看到代理类中有注入InvocationHandler的构造函数,和一个与接口相同的方法,用于调用InvocationHandler的invoke
方法,执行代理逻辑。
5.总结
JDK的Proxy类近乎完美地实现了动态代理,唯一的限制是,被代理类需要实现某个接口
,通过查阅Proxy.ProxyClassFactory中的源码可以知道,这是因为生成代理类时,需要通过反射,根据接口的方法来生成代理类中的方法。
Cglib基于Java字节码操作框架ASM实现了基于普通类的动态代理。在Spring框架中对两种动态代理都有使用,有空再详细聊聊Cglib的使用和原理。