Java动态代理

之前的文章说到了代理模式,代理模式的应用还是很多的。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 所示的结果。

图 . 使用代理模式提高性能


我们可以从如下两个角度来回答这个问题:

  • 把创建 BigImage 推迟到真正需要它时才创建,这样能保证前面程序运行的流畅性,而且能减少 BigImage 在内存中的存活时间,从宏观上节省了系统的内存开销。
  • 有些情况下,也许程序永远不会真正调用 ImageProxy 对象的 show() 方法——意味着系统根本无须创建 BigImage 对象。在这种情形下,使用代理模式可以显著地提高系统运行性能。

与此完全类似的是,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加载某个实体时,会对这个实体所有单端关联的另一个实体对象采用延迟加载,也就是使用 <many-to-one.../> 或 <one-to-one.../> 映射关联实体的情形。


二.动态代理

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




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章