1. 代理概念
代理顾名思义,代替别人做事。在设计模式中有代理模式,它的定义:为其他对象提供一种代理以控制对这种对象的访问。
代理模式中的代理类就是代替委托类完成事情。代理类需要完成的事情主要是对委托类进行预处理,过滤消息,把消息转发给委托类,以及事后处理消息。代理模式一大特点:对于编程人员而言看到的做事情代理类,而不是原来的委托类。
2. 两种方式实现代理
2.1 使用继承实现
继承指的是一个类(称为子类,子接口)继承另外的一个类(称为父类,父接口)。在代理模式中,委托类作为父类,代理类作为子类。在代理类中可以重写父类中需要被代理的方法。UML图和代码如下。
package cn.proxy;
public interface Dao {
void add();
}
package cn.proxy;
/**
*
* 类名称:Service
* 类描述: 委托类
* 修改人:pangfan
*
*/
public class Service implements Dao{
public void add() {
System.out.println("向数据库中添加数据!");
}
}
package cn.proxy;
/**
*
* 类名称:Service1
* 类描述: 代理类---给委托类添加上事务
* 修改人:pangfan
*
*/
public class Service1 extends Service{
public void add(){
/**
* 使用继承实现代理事务
*/
System.out.println("继承--开始事务");
super.add();
System.out.println("继承--结束事务");
}
}
客户端调用
package cn.proxy;
public class Client {
public static void main(String[] args) {
//测试继承实现的代理
Service t = new Service1();
t.add();
}
}
2.2 使用组合实现
组合也是关联关系的一种特例,它体现整体和部分之间的关系,但此时整体和部分是不可分的。在代理模式中,委托类作为部分,代理类作为整体。委托类是作为代理类的一部分存在,他们两个是不可分割的。在代码中表现为委托类作为代理类中德成员变量存在。UML图和代码如下。
package cn.proxy;
//事务代理
public class TranProxy implements Dao{
private Dao d;
//添加构造方法
public TranProxy(Dao d) {
super();
this.d = d;
}
public void add() {
System.out.println("组合--开始事务");
d.add();
System.out.println("组合--结束事务");
}
}
package cn.proxy;
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
//测试继承实现的代理
//Service t = new Service1();
//t.add();
//测试组合实现的代理
Service s = new Service();
Dao d = new TranProxy(s);
d.add();
}
继承和组合虽然都能够实现代理模式,但是我们平时都是使用组合方式的。这里面的原因有很多,我在这里主要只总结两个原因
1. 有一句话叫做“能用组合,不用继承”。继承的耦合度很高,不利于我们代码的扩展和修改。2. 使用组合能够使得代码更加灵活
举个例子,在上面的程序中只有事务的代理,现在需求改变,需要增加日志代理,并且要求事务和日志可以随意的改变执行的顺序。分别使用继承和组合做实现。
继承实现
package cn.proxy;
//使用继承实现代理---多件事情
public class Service2 extends Service{
public void add(){
/**
* 使用继承实现多件事情:记录事务和日志
*/
System.out.println("继承--开始事务");
super.add();
System.out.println("继承--结束事务");
System.out.println("继承--记录日志");
}
}
客户端调用
package cn.proxy;
public class Client {
public static void main(String[] args) {
//测试继承实现的代理
// Service t = new Service1();
// t.add();
//测试组合实现的代理
// Service s = new Service();
// Dao d = new TranProxy(s);
// d.add();
//测试继承实现多种事情的代理--两种不同的顺序需要调用两个不同的类
Service s1 = new Service1();
s1.add();
Service s2 = new Service2();
s2.add();
}
}
组合实现
package cn.proxy;
//日志代理
public class LogProxy implements Dao{
private Dao d;
public LogProxy(Dao m) {
super();
this.d = d;
}
public void add() {
System.out.println("组合--记录日志");
d.add();
}
}
客户端调用
package cn.proxy;
public class Client {
public static void main(String[] args) {
//测试继承实现的代理
// Service t = new Service1();
// t.add();
//测试组合实现的代理
// Service s = new Service();
// Dao d = new TranProxy(s);
// d.add();
//测试继承实现多种事情的代理--两种不同的顺序需要调用两个不同的类
// Service s1 = new Service1();
// s1.add();
// Service s2 = new Service2();
// s2.add();
//测试聚合实现多种事情的代理--两种不同的顺序只需要改变传递的参数
Service tt = new Service();
// 第二种
TranProxy tp1 = new TranProxy(tt);
LogProxy lp1 = new LogProxy(tp1);
Dao dao1 = lp1;
dao1.add();
// 第二种顺序
LogProxy lp2 = new LogProxy(tt);
TranProxy tp2 = new TranProxy(lp2);
Dao dao2 = tp2;
dao2.add();
}
}
从上面可以看出两种实现方式就开始不同了,继承的方式需要为两种不同的顺序分别添加新的代理类,但是组合的方式不需要;以此类推,如果事务代理和日志代理的顺序又有新的变化呢,那么继承方式的代理就需要不断的增加类;
而组合的方式只要不新增加代理就可以不用再增加类,类的个数不会发生变化。继承是的类之间的耦合度很高,不利于类之间的灵活性,所以实际我们能用组合的绝对不用继承,比如我们的装饰者模式和组合模式,他们都是使用的组合来实现的。但是我们从上面的例子中也可以看出,当新增加代理之后,无论是继承还是组合都需要新添加代理类,这种情况很容易出现一种类爆炸,所以代理还有可以进一步的改进,下面讲到的动态代理就可以解决这个问题。
3. 静态代理和动态代理
代理分为静态代理和动态代理两种。
静态代理:不隐藏创建代理类的过程。由程序员创建或特定工具自动生成源代码在对其编译。在程序运行前代理类的.class文件就已经存在。
动态代理:隐藏创建代理类的过程。在程序运行时运用反射机制动态创建而成
所以他们两个的区别就是在程序运行之前,.class文件是否已经存在。
我们上面使用继承和组合实现的代理都是静态代理。对于静态代理而言,我们每一个代理类只能为一个接口服务,这样子一来程序开发中必然会产生过多的代理,而且所有的代理操作除了嗲用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。
对于动态代理JDK和CGLIB都已经做了实现,我们在开发过程中使用他们封装好的动态代理很方便。
4. 总结
组合使得代理变得灵活,动态代理使得一个代理类完成全部的代理功能。动态代理使得我们系统的扩展性和灵活性大大提高。但是我们还要客观 的看待动态代理。
首先动态代理使用反射机制,反射肯定比直接调用要慢。
其次反射大量生成类文件可能引起Full GC(垃圾回收-Gabage Collection)造成性能影响。动态代理其实没有减少真正类的个数,只是将创建类 的过程隐藏起来了。所以如果在一个系统中使用很多的动态代理,反射生成的类文件加载后会存放在JVM运行时区中,当方法区满的时候,会引起Full GC。