代理模式(动态代理)
代理模式(动态)
我们知道, 静态代理需要自己创建代理对象, 如果需要的代理对象比较多的话, 代码就会比较繁琐
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
原理
利用反射机制在运行时创建代理类。
接口、被代理类不变,我们构建一个handler
类来实现InvocationHandler
接口。
特点
- 包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
- 类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
- 类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
- 类继承关系:该类的继承关系如图:
代码案例
还是以静态代理中的租房为案例
1.出租房子
com.test.Rent
// 出租房子
public interface Rent {
// 给了钱返回房子
Object rent(Object money);
}
2.业主(要出租房屋的人
com.test.Host
// 业主, 出租房子
public class Host implements Rent {
@Override
public Object rent(Object money) {
System.out.println("业主(真实对象): 房屋出租成功...价钱" + money);
return new Object(); // 假如Object就是出租的房子
}
}
3.代理(中介公司)
你只需要找中介租房即可, 不需要直接找业主, 使用动态代理, 如果需要的中介比较多, 我们就不需要创建许多的中介对象了
com.test.ProxyHandler
// 生产动态代理对象(生产中介公司)
public class ProxyHandler implements InvocationHandler {
// 保存要代理的对象-->业主
private Object target;
// 可以使用构造方法赋值, 也可以使用set方法, 只要最终target有值就行
public ProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行前
System.out.println("约时间");
System.out.println("看房子");
System.out.println("讲价钱, 签协议");
// 调用业主租房方法
method.invoke(target, args);
// 执行后
System.out.println("后期维护");
return null;
}
// 创建代理对象的方法
public Object createProxyedObj() {
// 创建代理, 传入要代理的对象的类加载器和这个对象所实现的接口
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
4.客户租房子
com.test.Customer
// 客户, 要租房子
public class Customer {
public static void main(String[] args) {
// 找中介, 传入要代理业主对象 --> (传入业主对象)
ProxyHandler proxy = new ProxyHandler(new Host());
// 创建代理对象(中介公司)
Rent rent = (Rent) proxy.createProxyedObj();
// 指定租房
rent.rent(3000);
}
}
结果: 显然和静态代理的结果是一样的
约时间
看房子
讲价钱, 签协议
业主(真实对象): 房屋出租成功...价钱3000
后期维护
优点
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。在本示例中看不出来,因为invoke方法体内嵌入了具体的外围业务(记录任务处理前后时间并计算时间差),实际中可以类似Spring AOP那样配置外围业务。
缺点
诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
静态代理和动态代理的区别
静态代理业务类只需要关注业务逻辑本身,保证了业务类的重用性。代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,需要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler invoke)。这样,在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使类职责更加单一,复用性更强。