之前的文章说到了代理模式,代理模式的应用还是很多的。Java这方面还支持动态代理,下面我们慢慢来说。
一.普通代理模式
如果客户端不想或者不能直接访问被调用对象——这种情况有很多原因,比如需要创建一个系统开销很大的对象,或者被调用对象在远程主机上,或者目标对象的功能还不足以满足需求……,而是额外创建一个代理对象返回给客户端使用,那么这种设计方式就是代理模式。相对于Java动态代理,普通代理模式也叫做静态代理模式。这里要说的例子就是Java代理模式的一种应用。
代理类和被代理的类都要实现相同的接口。
public interface Image
{
void show();
}
// 使用该 BigImage 模拟一个很大图片
public class BigImage implements Image
{
public BigImage()
{
try
{
// 程序暂停 3s 模式模拟系统开销
Thread.sleep(3000);
System.out.println("图片装载成功 ...");
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
// 实现 Image 里的 show() 方法
public void show()
{
System.out.println("绘制实际的大图片");
}
}
public class ImageProxy implements Image
{
private Image image; // 组合一个 image 实例,作为被代理的对象
public ImageProxy(Image image) // 使用抽象实体来初始化代理对象
{
this.image = image;
}
/**
* 重写 Image 接口的 show() 方法
* 该方法用于控制对被代理对象的访问,
* 并根据需要负责创建和删除被代理对象
*/
public void show()
{
if (image == null) // 只有当真正需要调用 image 的 show 方法时才创建被代理对象
{
image = new BigImage();
}
image.show();
}
}
//测试类 客户端
public class BigImageTest
{
public static void main(String[] args)
{
long start = System.currentTimeMillis();
// 程序返回一个 Image 对象,该对象只是 BigImage 的代理对象
Image image = new ImageProxy(null);
System.out.println("系统得到 Image 对象的时间开销 :" + (System.currentTimeMillis() - start));
// 只有当实际调用 image 代理的 show() 方法时,程序才会真正创建被代理对象。
image.show();
}
}
看到如图 6 所示的运行结果,读者应该能认同:使用代理模式提高了获取 Image 对象的系统性能。但可能有读者会提出疑问:程序调用 ImageProxy 对象的 show() 方法时一样需要创建 BigImage 对象啊,系统开销并未真正减少啊?只是这种系统开销延迟了而已啊?
上面程序初始化 image 非常快,因为程序并未真正创建 BigImage 对象,只是得到了 ImageProxy 代理对象——直到程序调用 image.show() 方法时,程序需要真正调用 BigImage 对象的 show() 方法,程序此时才真正创建 BigImage 对象。运行上面程序,看到如图 6 所示的结果。
我们可以从如下两个角度来回答这个问题:
与此完全类似的是,Hibernate 也是通过代理模式来“推迟”加载关联实体的时间,如果程序并不需要访问关联实体,那程序就不会去抓取关联实体了,这样既可以节省系统的内存开销,也可以缩短 Hibernate 加载实体的时间。Hibernate 的延迟加载(lazy load)本质上就是代理模式的应用,我们在过去的岁月里就经常通过代理模式来降低系统的内存开销、提升应用的运行性能,并结合了 Javassist 或 CGLIB 来动态地生成代理对象。Hibernate 对于 Set 属性延迟加载关键就在于 PersistentSet 实现类。在延迟加载时,开始 PersistentSet 集合里并不持有任何元素。但 PersistentSet 会持有一个 Hibernate Session,它可以保证当程序需要访问该集合时“立即”去加载数据记录,并装入集合元素。与 PersistentSet 实现类类似的是,Hibernate 还提供了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等实现类,它们的功能与 PersistentSet 的功能大致类似。
Hibernate中默认采用延迟加载的情况主要有以下几种
1 当调用session上的load()加载一个实体时,会采用延迟加载。
2 当session加载某个实体时,会对这个实体中的集合属性值采用延迟加载
3 当session加载某个实体时,会对这个实体所有单端关联的另一个实体对象采用延迟加载,也就是使用
二.动态代理
Java如果使用上面的这样静态代理,那每一个被代理的对象都需要创建一个代理类,会导致代码变得重复和冗余,如果能在运行的时候在创建一个代理类,它在作为任何被代理对象的代理类,这就用要用到动态代理。
还有一个用处,如果一个系统出现了很多的相同代码段,则可以将相同代码段定义为一个方法,让其他需要用到该方法的程序调用它-----这起到了解耦的作用,大大降低软件系统后期维护的复杂度。但是还是和特定的方法耦合了,这就要用到动态代理来解决问题。
注意JDK动态代理只能为接口创建,这是它的一个不足。例子如下:
import java.lang.reflect.*;
import java.lang.reflect.Proxy;
//接口
interface Dog{
void info();
void run();
}
//被代理的类,正真输出方法的对象。还可以有多个现实
class MuDog implements Dog{
public void info()
{
System.out.println("我是一只牧羊犬");
}
public void run()
{
System.out.println("我跑的快");
}
}
//动态代理类,运行时才加载需要代理的对象
class ProxyImp implements InvocationHandler{
private Object target; //定义为Object
//运行时在取得需要代理的类
public Object bind(Object target)
{
this.target=target;
//返回一个代理类,运行方法时是调用了下面的invoke方法
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy,Method method,Object[] args)throws Exception
{
System.out.println("--------动态代理开始--------");
Object result=method.invoke(target,args);
System.out.println("--------代理结束--------");
return result;
}
}
public class Test{
public static void main(String[] args){
ProxyImp proxy=new ProxyImp();
Dog dog=(Dog)proxy.bind(new MuDog()); //不仅可以有MuDog,还可以有不同的实现,却只有一个代理类
dog.info();
dog.run();
}
}
结果:
上面不仅让对象进行了解耦,还可以在invoke方法里面进行其他通用的处理,这也叫做AOP(面向切面编程)。基于反射的动态代理,灵活的实现解耦。没有实现接口的类就不能使用动态代理,可以使用Cglib来实现动态代理 。Cglib是采用动态创建子类的方法,代理类为动态创建的子类,对于final方法,无法进行代理。
参考:
http://www.ibm.com/developerworks/cn/java/j-lo-hibernatelazy/
http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html