Java静态代理与动态代理
一、静态代理和动态代理
静态代理需要和目标类实现共同的接口,可以通过代理类控制对目标类的访问致力于对外界隐藏目标类的具体信息。而常常与静态代理混淆的装饰器模式则是为了装饰目标类,所以我们常常将目标类作为参数传给装饰器,而代理类则是直接初始化某个确定的目标类,这两者关系下面这篇文章讲的比较清楚。
http://www.cnblogs.com/jaredlam/archive/2011/11/08/2241089.html
动态代理不同于静态代理,它是在代码运行期使用的的,可以很灵活的代理同一类型的目标。
二、实例讲解
下面我们直接通过实例代码对这两者进行分析,假设我们有一个字体提供类,有多重实现(网络提供、系统提供、磁盘提供)。假设我们需要在方法执行之前输出一条提示日志。
静态代理
静态代理需要和目标类实现同一个接口。
public interface FontProvider {
public Font getFont(String fontName);
}
实现磁盘提供类
public class FontProviderFromDisk implements FontProvider {
public Font getFont(String fontName) {
//从磁盘读取字体等操作
}
}
代理类
public class FontProviderProxy implements FontProvider {
Private FontProvider fontProvider;
public FontProviderProxy(FontProvider fontProvider) {
this.fontProvider=fontProvider;
}
public Font getFont(String fontName) {
System.out.println("do somethings");
fontProvider.getFont(fontName);
}
}
主函数
public class Main {
public static void main(String[] args) {
FontProvider fpp=new FontProviderProxy(new FontProviderFromDisk());
fpp.getFont();
}
}
如上所示,将目标类所谓参数传递给代理类,由代理类进行后续处理,屏蔽掉目标类的对象信息。但静态代理有个大问题,它必须与目标类实现同一个接口,当多个类都有类似需求时或者类中的方法过多时(获取图片类、获取音乐类),静态代理类的代码就十分庞大。这时,我们就可以通过动态代理解决这个问题。
动态代理
动态代理在运行时使用,动态代理中有一个关键的接口(InvocationHandler)和一个关键的类(Proxy),每一个动态代理类都继承自Proxy类,一般我们使用Proxy.newInstance方法创建一个代理实例。每一个动态代理类的实例都和一个InvocationHandler相关联。当我们通过代理实例调用某个方法时,实际上这个方法会被转发到InvocationHandler的invoke方法进行调用。
首先我们看一下创建实例的这三个参数分别代表什么
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);
Foo.class.getClassLoader():我们要代理的真实对象的类加载器
new Class<?>[] { Foo.class }:接口数组,表示我将要给我代理的对象提供的接口,如果提供了这个参数,那么真实对象就宣称实现了这些接口,即我们可以将这个代理对象强制转换为接口数组中的任意一个,同时可以通过这些接口的方法调用真实对象的方法(未在接口中声明的方法,即便真实对象中有这些方法,代理类也不能调用,常常用来屏蔽某些方法)。
handler:InvocationHandler接口的实现类,表示这个动态代理实例要关联到哪一个InvocationHandler上,代理逻辑就在此类中实现
接下来我们看一下InvocationHandler接口中的invoke方法,此接口中只有这一个方法,在此方法中通过反射技术来完成动态代理对象的方法调用。
Object invoke(Object proxy, Method method, Object[] args)
proxy:指创建出的动态代理对象,一般无需人工操作
method:指我们所要调用的动态代理对象的某个方法的Method实例
args:方法参数
实例代码
public interface FontProvider {
public Font getFont(String fontName);
}
实现磁盘提供类
public class FontProviderFromDisk implements FontProvider {
public Font getFont(String fontName) {
//从磁盘读取字体等操作
}
}
实际的代理逻辑就在此类的invoke方法中执行
public ProxyHandler implements InvocationHandler {
//此处类型为Object,增加代理的复用性
private Object target;
public ProxyHandler(Object target) {
this.target=target
}
public Object invoke(Object proxy , Method method , Object[] args)throws Throwable {
//此处即可进行代理操作
object result=method.invoke(target,args);
return result;
}
}
public class Main {
public static void main(String[] args) {
FontProvider fp=new FontProviderFromDisk();
ProxyHandler proxyHandler=new ProxyHandler;
FontProvider proxy=(FontProvider)Proxy.newInstance(fp.getClass().getClassLoader(),fp.getClass().getInterfaces(),proxy);
proxy.getFont("宋体");
}
}
继续探索:
如果在上述主函数加上这行代码
System.out.println(proxy.getClass().getName());
你会惊奇的发现输出结果竟然是
$Proxy0
原因就是通过Proxy.newInstance()创建的代理对象是JVM在运行时动态生成的一个对象,它既不是我们定义的那组接口类型(Proxy),也不是InnovationHandler类型,它的命名方式就是以$Proxy开头,最后的数字表示对象的标号,也就是InvocationHandler的invoke方法的第一个参数值。
动态代理在代码量较小时作用并不明显,但随着业务不断庞大,作用就凸显出来了,主要有以下几点。
1. Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;
2. 可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;
3. 解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变。